diff --git a/site_ansto/instrument/TEST_SICS/sics_test_services b/site_ansto/instrument/TEST_SICS/sics_test_services index 30210410..a520f9cf 100644 --- a/site_ansto/instrument/TEST_SICS/sics_test_services +++ b/site_ansto/instrument/TEST_SICS/sics_test_services @@ -1,6 +1,6 @@ # PORT NUMBERS FOR SICS TESTING -# $Revision: 1.3 $ -# $Date: 2007-02-20 01:15:34 $ +# $Revision: 1.4 $ +# $Date: 2009-12-09 06:07:45 $ # Author: Ferdi Franceschini (ffr@ansto.gov.au) # Last revision by $Author: ffr $ @@ -48,87 +48,87 @@ pmc3-taipan 62732/tcp pmc4-taipan 62733/tcp pmc5-taipan 62734/tcp -telnet-echidna 60000/tcp -telnet-echidna 60000/udp -interrupt-echidna 60001/udp -server-echidna 60002/tcp -quieck-echidna 60003/udp +sics-telnet-echidna 60000/tcp +sics-telnet-echidna 60000/udp +sics-interrupt-echidna 60001/udp +sics-server-echidna 60002/tcp +sics-quieck-echidna 60003/udp -telnet-val-echidna 60010/tcp -telnet-val-echidna 60010/udp -interrupt-val-echidna 60011/udp -server-val-echidna 60012/tcp -quieck-val-echidna 60013/udp +sics-telnet-val-echidna 60010/tcp +sics-telnet-val-echidna 60010/udp +sics-interrupt-val-echidna 60011/udp +sics-server-val-echidna 60012/tcp +sics-quieck-val-echidna 60013/udp -telnet-wombat 60100/tcp -telnet-wombat 60100/udp -interrupt-wombat 60101/udp -server-wombat 60102/tcp -quieck-wombat 60103/udp +sics-telnet-wombat 60100/tcp +sics-telnet-wombat 60100/udp +sics-interrupt-wombat 60101/udp +sics-server-wombat 60102/tcp +sics-quieck-wombat 60103/udp -telnet-val-wombat 60110/tcp -telnet-val-wombat 60110/udp -interrupt-val-wombat 60111/udp -server-val-wombat 60112/tcp -quieck-val-wombat 60113/udp +sics-telnet-val-wombat 60110/tcp +sics-telnet-val-wombat 60110/udp +sics-interrupt-val-wombat 60111/udp +sics-server-val-wombat 60112/tcp +sics-quieck-val-wombat 60113/udp -telnet-kowari 60300/tcp -telnet-kowari 60300/udp -interrupt-kowari 60301/udp -server-kowari 60302/tcp -quieck-kowari 60303/udp +sics-telnet-kowari 60300/tcp +sics-telnet-kowari 60300/udp +sics-interrupt-kowari 60301/udp +sics-server-kowari 60302/tcp +sics-quieck-kowari 60303/udp -telnet-val-kowari 60310/tcp -telnet-val-kowari 60310/udp -interrupt-val-kowari 60311/udp -server-val-kowari 60312/tcp -quieck-val-kowari 60313/udp +sics-telnet-val-kowari 60310/tcp +sics-telnet-val-kowari 60310/udp +sics-interrupt-val-kowari 60311/udp +sics-server-val-kowari 60312/tcp +sics-quieck-val-kowari 60313/udp -telnet-quokka 60400/tcp -telnet-quokka 60400/udp -interrupt-quokka 60401/udp -server-quokka 60402/tcp -quieck-quokka 60403/udp +sics-telnet-quokka 60400/tcp +sics-telnet-quokka 60400/udp +sics-interrupt-quokka 60401/udp +sics-server-quokka 60402/tcp +sics-quieck-quokka 60403/udp -telnet-val-quokka 60410/tcp -telnet-val-quokka 60410/udp -interrupt-val-quokka 60411/udp -server-val-quokka 60412/tcp -quieck-val-quokka 60413/udp +sics-telnet-val-quokka 60410/tcp +sics-telnet-val-quokka 60410/udp +sics-interrupt-val-quokka 60411/udp +sics-server-val-quokka 60412/tcp +sics-quieck-val-quokka 60413/udp -telnet-platypus 60500/tcp -telnet-platypus 60500/udp -interrupt-platypus 60501/udp -server-platypus 60502/tcp -quieck-platypus 60503/udp +sics-telnet-platypus 60500/tcp +sics-telnet-platypus 60500/udp +sics-interrupt-platypus 60501/udp +sics-server-platypus 60502/tcp +sics-quieck-platypus 60503/udp -telnet-val-platypus 60510/tcp -telnet-val-platypus 60510/udp -interrupt-val-platypus 60511/udp -server-val-platypus 60512/tcp -quieck-val-platypus 60513/udp +sics-telnet-val-platypus 60510/tcp +sics-telnet-val-platypus 60510/udp +sics-interrupt-val-platypus 60511/udp +sics-server-val-platypus 60512/tcp +sics-quieck-val-platypus 60513/udp -telnet-pelican 60600/tcp -telnet-pelican 60600/udp -interrupt-pelican 60601/udp -server-pelican 60602/tcp -quieck-pelican 60603/udp +sics-telnet-pelican 60600/tcp +sics-telnet-pelican 60600/udp +sics-interrupt-pelican 60601/udp +sics-server-pelican 60602/tcp +sics-quieck-pelican 60603/udp -telnet-val-pelican 60610/tcp -telnet-val-pelican 60610/udp -interrupt-val-pelican 60611/udp -server-val-pelican 60612/tcp -quieck-val-pelican 60613/udp +sics-telnet-val-pelican 60610/tcp +sics-telnet-val-pelican 60610/udp +sics-interrupt-val-pelican 60611/udp +sics-server-val-pelican 60612/tcp +sics-quieck-val-pelican 60613/udp -telnet-taipan 60700/tcp -telnet-taipan 60700/udp -interrupt-taipan 60701/udp -server-taipan 60702/tcp -quieck-taipan 60703/udp +sics-telnet-taipan 60700/tcp +sics-telnet-taipan 60700/udp +sics-interrupt-taipan 60701/udp +sics-server-taipan 60702/tcp +sics-quieck-taipan 60703/udp -telnet-val-taipan 60710/tcp -telnet-val-taipan 60710/udp -interrupt-val-taipan 60711/udp -server-val-taipan 60712/tcp -quieck-val-taipan 60713/udp +sics-telnet-val-taipan 60710/tcp +sics-telnet-val-taipan 60710/udp +sics-interrupt-val-taipan 60711/udp +sics-server-val-taipan 60712/tcp +sics-quieck-val-taipan 60713/udp diff --git a/site_ansto/instrument/config/environment/magneticField/sct_bruker_BEC1.tcl b/site_ansto/instrument/config/environment/magneticField/sct_bruker_BEC1.tcl new file mode 100644 index 00000000..b86fd0cd --- /dev/null +++ b/site_ansto/instrument/config/environment/magneticField/sct_bruker_BEC1.tcl @@ -0,0 +1,1366 @@ +## +# @brief Handle exceptions caught by a 'catch' command. +# Note: You must use 'error' not 'return -code error' to +# raise errors from within a catch block. +# +# @param status, the status returned by the 'catch' command. +# @param message, the message set by the 'catch' command. +# +# Call this as the last command in the command block or +# for loop which encloses the 'catch' +proc handle_exception {status message} { + switch $status { + 0 { + # TCL_OK, This is raised when you just drop out of the + # bottom of a 'catch' command. + return -code ok + } + 1 { + # TCL_ERROR + return -code error "([info level -1]) $message" + } + 2 { + # TCL_RETURN + return -code return "$message" + } + 3 { + # TCL_BREAK + return -code break + } + 4 { + # TCL_CONTINUE + return -code continue + } + default { + # Propogate user defined return codes with message + return -code $status "$message" + } + } +} + +# Define procs in ::scobj::xxx namespace +# MakeSICSObj $obj SCT_ +# The MakeSICSObj cmd adds a /sics/$obj node. NOTE the /sics node is not browsable. + +## +# /*-------------------------------------------------------------------------- +# B R U K E R B - E C 1 D R I V E R +# Power supply for 1-Tesla magnet +# +# @file: This file contains the implementation of a driver for SICS for the +# Bruker B-EC1 power supply for the 1-Tesla magnet implemented as a +# scriptcontext object in TCL. +# +# @author: Arndt Meier, ANSTO, 2009-08-31 +# @brief: driver for Bruker 1-Tesla magnet power supply (in TCL) +# @version: 20091002 for sics2_4 +# +# known bugs: betaa stage - version 1.1.0 +# ----------------------------------------------------------------------------*/ + +# 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. + +# Bugs, limitations, changes: +# - checks whether the reply from the device matches the query - if not, it discards the reply. +# - added LSB_Err byte 1 interpretation (water failure). Added help notes +# for the user for the status byte text nodes. +# - MAJOR PROBLEM: Although the driver seems to be doing just about everything +# right, the BEC1 device shuts itself off (changes to DCpower=0) and goes +# into standby after between 30 secs to 3 minutes. There is nothing in the +# TCP/IP communication log that would suggest a fault on the driver's side. +# The only cause the manufacturer could think of is that we are sending +# commands to the device too quickly. +# - We introduced a new global variable bruker_BEC1_MIN_TIME_BETWEEN_COMMANDS +# that helps us to insure this time in milliseconds is the minimum time +# between sending 2 queries or commands to the device following a Bruker +# recommendation. Supposedly a value between 50 to 200milliseconds should +# suffice to prevent a collapse of communication as described above, alas it +# did not have the expected effect so far. +# - Fixed the StateMachineStatusByteTxt bug - now decodes the +# status byte correctly into its corresponding error text. + +# Default parameters for the device +namespace eval ::scobj::bruker_BEC1 { + # The BEC1 cannot take commands faster than 1 per 50 milliseconds - increase to 100 or 200 + # if necessary. If commands come in too fast, the DCpower may switch itself off. + set bruker_BEC1_MIN_TIME_BETWEEN_COMMANDS 50 + # set a start value for the time of the last command send to or read from the device + set bruker_BEC1_timeLastCommand [clock clicks -milliseconds] + + # 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 bruker_BEC1_sct_obj_name "UNKNOWN" + # Last query and write command sent to the device - for debugging and other tasks + set bruker_BEC1_lastQueryCmd " " + set bruker_BEC1_lastWriteCmd " " + # provide a global variable holding the path to the nodes + set bruker_BEC1_path2nodes "/sample/ma1" + # terminator string for serial communication + set bruker_BEC1_term "" + # variables that are identical to node names but are needed internally as well + # temperature tolerance in Ampere + set bruker_BEC1_tolerance 0.1 + # magnetic field strength difference between set and actual field strength at + # which the power supply changes from idle to driving - is driveTolerance*tolerance + set bruker_BEC1_driveTolerance 0.2 + # Last 2 error messages + set bruker_BEC1_errMsg "none" + set bruker_BEC1_errMsg2 "none" + # Polarity switching unit installed or absent? + set bruker_BEC1_polarityUnitAbsent false + # value indicating an error or invalid value + set bruker_BEC1_errValue -9999.99 + # upper and lower limit in mT + set bruker_BEC1_upperlimit 1000.0 + set bruker_BEC1_lowerlimit 0.0 + # magnetic field units are milliTesla + set bruker_BEC1_magneticFiledUnits "T" + # bruker_BEC1 status byte + set bruker_BEC1_statusByte -1 + # set device ID to unknown + set this_sDeviceID "Unknown_Device" + # set self-test result to unknown + set this_selfTestResult -1 + + #set tc_dfltURL ca5-[instname] + array set moxaPortMap {1 4001 2 4002 3 4003 4 4004} + +########### Initialisation ############################# +## +# Initialise the bruker_BEC1: +# Checks the device ID, resets the device, checks the self-test, sets the communication +# protocol 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 bruker_BEC1_init {sct_controller tc_root} { + if [ catch { + # set the communication protocol: terminator , bps 9600 baud, 7 data bits + + # 1 stop bit + odd parity bit + # puts "setting serial communication parameters" +# hset $tc_root/other/cfgProtocol_comm "COMM 1,5,1" + # Query the device ID +# 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::bruker_BEC1::this_sDeviceID data +# puts "sct_bruker_BEC1.tcl: connected to device $::scobj::bruker_BEC1::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 +# 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::bruker_BEC1::this_selfTestResult [hval $tc_root/other/selftest] +# if {$::scobj::bruker_BEC1::this_selfTestResult == 0} { +# puts "sct_bruker_BEC1.tcl: Lakeshore $::scobj::bruker_BEC1::bruker_BEC1_LSmodel self-test ok." +# } else { +# puts "sct_bruker_BEC1.tcl: The Lakeshore $::scobj::bruker_BEC1::bruker_BEC1_LSmodel failed its self-test." +# } + # Set the default tolerances for the setpoint magentic field strength + hset $tc_root/emon/tolerance $::scobj::bruker_BEC1::bruker_BEC1_tolerance +} message ] { + return -code error "in bruker_BEC1_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 expectedLen} { + if [ catch { + set ::scobj::bruker_BEC1::bruker_BEC1_lastQueryCmd "$cmd" + set tNow [clock clicks -milliseconds] + #set diff1 [expr $tNow - $::scobj::bruker_BEC1::bruker_BEC1_timeLastCommand] + while {[expr $tNow - $::scobj::bruker_BEC1::bruker_BEC1_timeLastCommand] < $::scobj::bruker_BEC1::bruker_BEC1_MIN_TIME_BETWEEN_COMMANDS} { + set tNow [clock clicks -milliseconds] + } + #set diff2 [expr $tNow - $::scobj::bruker_BEC1::bruker_BEC1_timeLastCommand] + sct send $cmd$::scobj::bruker_BEC1::bruker_BEC1_term + # puts "sct send !$cmd$::scobj::bruker_BEC1::bruker_BEC1_term!" + set ::scobj::bruker_BEC1::bruker_BEC1_timeLastCommand $tNow + #puts "diff1:$diff1, diff2:$diff2 $cmd" + } message ] { + return -code error "in getValue: $message. Last query command: $::scobj::bruker_BEC1::bruker_BEC1_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 bXtract if set to 'X' the value/number is extratced from the response and shown +# as the node values - allows float and int values as opposed to text. This +# is important if this value shall be logged via Nexus. +# @return idle Always returns system state idle - command sequence completed. + proc rdValue {expectedLength} { + set data [sct result] + # puts "rdValue(): result is $data" + # broadcast rdValue "rdValue(): result is $data" + #puts "sct result !$data!" + set rData $data + # Do we get the answer to the question we asked?! Occasionally the BEC1 is sending info on its own. + if { 0 != [string compare -length 4 $data $::scobj::bruker_BEC1::bruker_BEC1_lastQueryCmd]} { + # Discard if it is not the reply to our query + return idle + } + set catch_status [ catch { + # Continue as normal + switch -glob -- $data { + "ASCERR:*" { + puts "ASCERR in rdValue: Last query command: $::scobj::bruker_BEC1::bruker_BEC1_lastQueryCmd" + sct geterror $data + } + default { + if { [string length $data] > $expectedLength } { + # Discard - this it is not the reply to our query because this is what happened: + #ERROR: in rdValue: in analyseStatusByte(): in decodeErrByte(): syntax error in expression "0xHF": extra tokens at end of expression. errByte: HF, errList: 08 {Inrush procedure error} 01 {Current limit exceeded} + # . statusByteString: STA/0CHF/-0.0002T. Last query command: STA/ + return idle +puts "Rejected !$data! as reply to $::scobj::bruker_BEC1::bruker_BEC1_lastQueryCmd" + } + set orgdata $data + set data [ExtractValue $data $::scobj::bruker_BEC1::bruker_BEC1_lastQueryCmd] + if {$data != [sct oldval]} { + sct oldval $data + sct update $data + sct utime readtime + + if {1==0} { + # Not in use - always results in error - we may not have the polarity reversal unit + # We are done here - below are some special cases + # if polarity unit is not installed, don't show repetetive error messages + if { 0 == [string compare -length 4 $::scobj::bruker_BEC1::bruker_BEC1_lastQueryCmd "POL/"]} { + if {$data == 0} { + set ::scobj::bruker_BEC1::bruker_BEC1_polarityUnitAbsent true + } else { + set ::scobj::bruker_BEC1::bruker_BEC1_polarityUnitAbsent false + } + } + } + + # When reading the status byte, provide a human readable interpretation + if { 0 == [string compare -length 4 $rData "STA/"]} { + # we have to extract again without stripping trailing characters that + # normally represent a physical unit like Ampere but could here be part + # of a hexadecimal value + #puts "rdValue: Interpreting status byte information: $orgdata $::scobj::bruker_BEC1::bruker_BEC1_lastQueryCmd" + # Ananlyse the status bytes string containing the 4 status bytes and present the + # result in human readable form (Text instead of hex number) + analyseStatusByte $rData + } + + if {1==0} { + # Not in use - we are using serial connection via a moxabox so we don't need the + # ethernet address of the Bruker device + # When reading the ethernet address in revers hex notation, provide a normal decimal interpretation + if { 0 == [string compare -length 4 $::scobj::bruker_BEC1::bruker_BEC1_lastQueryCmd "ETH/"]} { + # we have to extract again without stripping trailing characters that + # normally represent a physical unit like Ampere but could here be part + # of a hexadecimal value + # Beware that Bruker uses reverse notation. + set data [ExtractValue $orgdata $::scobj::bruker_BEC1::bruker_BEC1_lastQueryCmd] + #puts "rdValue: Interpreting status byte information: $::scobj::bruker_BEC1::bruker_BEC1_lastQueryCmd" + #pwrCtrl EthernetAddrHex 1 0 1 0 text spy {ETH/} {rdValue} {} {setValue} {}\ + hset $::scobj::bruker_BEC1::bruker_BEC1_path2nodes/pwrCtrl/EthernetAddrDec "UNKNOWN" + } + } + } + } + } + return idle + } message ] + handle_exception $catch_status $message +} + + + + + +## +# @brief Does what rdValue() does plus it checks if the current is in tolerance. +# inTolerance is the default nextState after getValue() for read node pwrCtrl/DC_power. +# If the device is switched off, current is reported to be in tolerance so that +# slow return to ambient conditions can be carried out +# @return idle Always returns system state idle - command sequence completed. +proc inTolerance {expectedLength} { + set tc_root $::scobj::bruker_BEC1::bruker_BEC1_path2nodes + set data [sct result] + # puts "inT result !$data!" + # Do we get the answer to the question we asked?! Occasionally the BEC1 is sending info on its own. + if { 0 != [string compare -length 4 $data $::scobj::bruker_BEC1::bruker_BEC1_lastQueryCmd]} { + # Discard if it is not the reply to our query + return idle + } + set catch_status [ catch { + set oldval [sct oldval] + # puts "inTolerance(): data=$data oldval=$oldval" + switch -glob -- $data { + "ASCERR:*" { + puts "ASCERR in inTolerance: Last query command: $::scobj::bruker_BEC1::bruker_BEC1_lastQueryCmd" + sct geterror $data + } + default { + # DCP/ 0 + if { [string length $data] > $expectedLength } { + # Discard - this it is not the reply to our query because this is what happened: + #ERROR: in rdValue: in analyseStatusByte(): in decodeErrByte(): syntax error in expression "0xHF": extra tokens at end of expression. errByte: HF, errList: 08 {Inrush procedure error} 01 {Current limit exceeded} + # . statusByteString: STA/0CHF/-0.0002T. Last query command: STA/ +puts "Rejected !$data! as reply to $::scobj::bruker_BEC1::bruker_BEC1_lastQueryCmd" + return idle + } + set data [ExtractValue $data $::scobj::bruker_BEC1::bruker_BEC1_lastQueryCmd] + if {$data != $oldval} { + if {$oldval == "UNKNOWN"} { + sct utime timecheck + } + sct oldval $data + sct update $data + sct utime readtime + } + } + } + # puts "inTolerance $::scobj::bruker_BEC1::bruker_BEC1_sct_obj_name data:$data" + # now update the manual nodes reporting whether the actual field strength + # is within tolerance of the corresponding setpoint + if {$data == 0 } { + # DC power switched off + hset $tc_root/emon/monMode "idle" + hset $tc_root/emon/isInTolerance "inTolerance" + hset $tc_root/status "idle" + } else { + set intol [checktol $tc_root] + if {$intol==0} { sct utime timecheck } + set nodename $tc_root/sensor/NominalOutpCurrent + set setpt [hval $nodename] + set nodename $tc_root/sensor/DesiredCurrent + set NominalOutpCurrent [hval $nodename] + # puts "inTolerance(): comparing sensor/setpoint=$setpt with actual sensorValue=$temp" + set diff [expr abs($setpt - $NominalOutpCurrent)] + if {$diff > $::scobj::bruker_BEC1::bruker_BEC1_driveTolerance} { + # ERROR: node /sics/ma1/emon/monMode not found. Last query command: DCP/ + set nodename $tc_root/emon/monMode + hset $nodename "drive" + hset $tc_root/status "busy" + } else { + set nodename $tc_root/sensor/NominalOutpCurrent + hsetprop $nodename driving 0 + set nodename $tc_root/emon/monMode + hset $nodename "monitor" + hset $tc_root/status "idle" + } + } + # puts "inTolerance 4 $::scobj::bruker_BEC1::bruker_BEC1_sct_obj_name data:$data" + return idle + } message ] + handle_exception $catch_status $message + # puts "Leaving inTolerance idx:$CtrlLoopIdx" +} + + +################## 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) +# @return nextState Is typically noResponse as the device does not acknowledge the write request +proc setValue {tc_root nextState cmd } { + # tc_root is not being used - however, don't remove so we can use the + # same calling mask as for setPoint() or other $wrFunc + if [ catch { + set par [sct target] + set ::scobj::bruker_BEC1::bruker_BEC1_lastWriteCmd "$cmd$par" + set tNow [clock clicks -milliseconds] + while {[expr $tNow - $::scobj::bruker_BEC1::bruker_BEC1_timeLastCommand] < $::scobj::bruker_BEC1::bruker_BEC1_MIN_TIME_BETWEEN_COMMANDS} { + set tNow [clock clicks -milliseconds] + } + sct send "$cmd$par$::scobj::bruker_BEC1::bruker_BEC1_term" + set ::scobj::bruker_BEC1::bruker_BEC1_timeLastCommand $tNow + if { 0 == [string compare -length 4 $cmd "RST=0"] } { + # Reset error messages - also update the node displaying the last error + set ::scobj::bruker_BEC1::bruker_BEC1_errMsg "none" + set ::scobj::bruker_BEC1::bruker_BEC1_errMsg2 "none" + hset $::scobj::bruker_BEC1::bruker_BEC1_path2nodes/emon/lastErrorMsg $::scobj::bruker_BEC1::bruker_BEC1_errMsg + hset $::scobj::bruker_BEC1::bruker_BEC1_path2nodes/emon/lastErrorMsg2 $::scobj::bruker_BEC1::bruker_BEC1_errMsg2 + } + } message ] { + return -code error "in setValue: $message. While sending command: $::scobj::bruker_BEC1::bruker_BEC1_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 magnetic field strength. +# @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 +# @return nextState +proc setDesiredField {tc_root nextState cmd} { + if [ catch { + #puts "executing setDesiredField ($tc_root $nextState $cmd)" + set ns ::scobj::lh45 + set par [sct target] + + #hset $tc_root/status "busy" + set wrStatus [sct writestatus] + if {$wrStatus == "start"} { + # Called by drive adapter + # puts "setDesiredField(): driving set to 1" + set nodename $tc_root/sensor/setpoint + hsetprop $nodename driving 1 + } + #puts "setDesiredField(wrStatus=$wrStatus): sct send $cmd$par$::scobj::bruker_BEC1::bruker_BEC1_term" + set tNow [clock clicks -milliseconds] + #set diff1 [expr $tNow - $::scobj::bruker_BEC1::bruker_BEC1_timeLastCommand] + while {[expr $tNow - $::scobj::bruker_BEC1::bruker_BEC1_timeLastCommand] < $::scobj::bruker_BEC1::bruker_BEC1_MIN_TIME_BETWEEN_COMMANDS} { + set tNow [clock clicks -milliseconds] + } + sct send "$cmd$par$::scobj::bruker_BEC1::bruker_BEC1_term" + } message ] { + return -code error "in setDesiredField: $message. Last write command: $::scobj::bruker_BEC1::bruker_BEC1_lastWriteCmd" + } + return $nextState +} + + +## +# @brief Sets the desired nominal current. +# @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 +# @return nextState +proc setDesiredCurrent {tc_root nextState cmd} { + if [ catch { + #puts "executing setDesiredCurrent ($tc_root $nextState $cmd)" + set ns ::scobj::lh45 + set par [sct target] + + #hset $tc_root/status "busy" + set wrStatus [sct writestatus] + if {$wrStatus == "start"} { + # Called by drive adapter + # puts "setDesiredCurrent(): driving set to 1" + set nodename $tc_root/sensor/NominalOutpCurrent + hsetprop $nodename driving 1 + } + #puts "setDesiredCurrent(wrStatus=$wrStatus): sct send $cmd$par$::scobj::bruker_BEC1::bruker_BEC1_term" + set tNow [clock clicks -milliseconds] + #set diff1 [expr $tNow - $::scobj::bruker_BEC1::bruker_BEC1_timeLastCommand] + while {[expr $tNow - $::scobj::bruker_BEC1::bruker_BEC1_timeLastCommand] < $::scobj::bruker_BEC1::bruker_BEC1_MIN_TIME_BETWEEN_COMMANDS} { + set tNow [clock clicks -milliseconds] + } + sct send "$cmd$par$::scobj::bruker_BEC1::bruker_BEC1_term" + set ::scobj::bruker_BEC1::bruker_BEC1_timeLastCommand $tNow + } message ] { + return -code error "in setDesiredCurrent: $message. Last write command: $::scobj::bruker_BEC1::bruker_BEC1_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} { +# broadcast "DEBUG: in drivestatus. Last write command: $::scobj::bruker_BEC1::bruker_BEC1_lastWriteCmd" + if [sct driving] { + return busy + } else { + return idle + } + } + +## +# @brief Stops driving at current magnetic field strenght. +# Sets the setpoint to the current magnetic field strength +# @param tc_root string variable holding the path to the object's base node in sics +# @return idle Indicates that the device is ready for the next command + proc halt {tc_root} { + # stop driving at current field strength or current + set sensorValue $tc_root/sensor/DesiredCurrent + set nodename $tc_root/sensor/NominalOutpCurrent + hset $nodename [hval $sensorValue] + hsetprop $nodename driving 0 + return idle + } + +############# Auxiliary functions ########################################## + +## +# @brief Extracts the actual value or number from the response message of the device. The +# Bruker BEC1 repeats the query command send followed by its response and often as +# well a physical unit like A for Ampere, T for Tesla etc. This subroutine extracts +# the number from the string. +proc ExtractValue {response lastQueryCmd} { + # A typical argument may look like this: "CHF/-0.0002T" or "FLD/ 0.0000Tref" + # However, with STA/0000C100 and ETH/95EC3E29 we have to be careful not to + # strip a letter that is part of a hex number. + if [ catch { + set extractedValue 0 + set queryStringLength [string length $lastQueryCmd] + set responseStringLength [string length $response] + incr responseStringLength -1 + set units [string range $response $responseStringLength $responseStringLength] + set max 4 + # The length of trailing units is max 4 characters long, like "Tref" + while {[string is alpha -strict $units] && $max > 0} { + # We have to strip off the physical unit at the end of the string like 'T' or 'Tref' for Tesla "CHF/-0.0002T" + incr responseStringLength -1 + incr max -1 + set units [string range $response $responseStringLength $responseStringLength] + } + set extractedValue [string range $response $queryStringLength $responseStringLength] + # Check whether there is an error flag instead of a value + if { 0 == [string compare -length 1 $extractedValue "E"] } { + # we can ignore a polarity query error if no polarity unit is installed - else show the error message + set ::scobj::bruker_BEC1::bruker_BEC1_errMsg2 $::scobj::bruker_BEC1::bruker_BEC1_errMsg + set ::scobj::bruker_BEC1::bruker_BEC1_errMsg "Unknown error. The device replied $response" + switch -glob $extractedValue { + "E01*" {set ::scobj::bruker_BEC1::bruker_BEC1_errMsg "FUNCTION ERROR - Function not supported, $response"} + "E02*" {set ::scobj::bruker_BEC1::bruker_BEC1_errMsg "ARGUMENT ERROR - command argument contains unexpected characters, $response"} + "E03*" {set ::scobj::bruker_BEC1::bruker_BEC1_errMsg "PORT NOT AVAILABLE, $response"} + "E04*" {set ::scobj::bruker_BEC1::bruker_BEC1_errMsg "LOCAL ERROR - access denied, check LocalRemoteState setting, $response"} + "E05*" {set ::scobj::bruker_BEC1::bruker_BEC1_errMsg "RANGE ERROR - the arguments are out of the allowed range, $response"} + "E06*" {set ::scobj::bruker_BEC1::bruker_BEC1_errMsg "MODE ERROR - access denied, external reference active, $response"} + "E07*" {set ::scobj::bruker_BEC1::bruker_BEC1_errMsg "ERROR PENDING, $response"} + "E09*" {set ::scobj::bruker_BEC1::bruker_BEC1_errMsg "DC ERROR, $response"} + "E99*" {set ::scobj::bruker_BEC1::bruker_BEC1_errMsg "NO TCP/IP COMMUNICATION, $response"} + } + set extractedValue $::scobj::bruker_BEC1::bruker_BEC1_errValue + # update the node lastErrorMsg + hset $::scobj::bruker_BEC1::bruker_BEC1_path2nodes/emon/lastErrorMsg $::scobj::bruker_BEC1::bruker_BEC1_errMsg + hset $::scobj::bruker_BEC1::bruker_BEC1_path2nodes/emon/lastErrorMsg2 $::scobj::bruker_BEC1::bruker_BEC1_errMsg2 + #if { 0 != [string compare -length 4 $lastQueryCmd "POL/"] \ + # || $::scobj::bruker_BEC1::bruker_BEC1_polarityUnitAbsent == false} { + #} + } + #puts "ExtractValue(): response:$response, extractedValue:$extractedValue" + } message ] { + return -code error "in ExtractValue(): $message. Last device response: $response" + } + return $extractedValue +} + + +# error messages according to Oak Ridge version of the manual +# I suspect that the difference are that we do not have a polarity reversal unit. +# State machine; hex Value; State +# 00 Neutral state +# 05 Reset external and field reference +# 06 Ramp DAC to 0 +# 07 Test DAC=0 +# 08 Test ADC=0 +# 09 Set DC off +# 0A Set time 1 second +# 0B Wait time +# 0C Return to neutral state +# 0F Reset external and field reference +# 10 Set DAC to 0 +# 11 Test ADC=0 +# 12 Set Inrush on +# 13 Set time 1 second +# 14 Wait time +# 15 Set DC ON +# 16 Set time 1 second +# 17 Wait time +# 18 Set Inrush OFF +# 19 Return to neutral state +# #Set polarity positive +# 19 Ramp DAC to 0 +# 1A Reset ext / BH-15 ref. +# 1B Test DAC=0 +# 1C Test ADC=0 +# 1D Set time=2 seconds +# 1E Wait time +# 1F Reset SEM +# 20 Set time=2 seconds +# 21 Wait time +# 22 Set polarity positive, set timeout +# 23 Wait pol. read back, check timeout +# 24 Set SEM +# 25 Set time=1 second +# 26 Wait time +# 27 Set ext / BH-15 ref? +# 28 Return to neutral state +# 2D Ramp DAC to 0 +# 2E Reset ext / BH-15 ref +# 2F Test DAC=0 +# 30 Test ADC=0 +# 31 Set time=2 seconds +# 32 Wait time +# 33 Reset SEM +# 34 Set time=2 seconds +# 35 Wait time +# 36 Set polarity negative, set timeout +# 37 Wait polarity read back, check timeout +# 38 Set SEM +# 39 Set time=1 second +# 3A Wait time +# 3B Set ext / BH-15 ref? +# 3C Return to neutral state + +## +# @brief Matches the error byte hexadecimal value to its error message text +# Multiple conditioins may apply. +# @param errByte the error byte as a hexadecimal value +# @param errList 2-column list of error hex value and corresponding message text +# @return returnval String holding the corresponding error message +proc decodeErrByte {errByte errList} { + set errorText "" + set delim ", " + #puts "decodeErrByte: errByte:$errByte" + if [ catch { + # convert to decimal for calculations + set decErrByte [expr 0x$errByte] + foreach {hexVal errText} $errList { + #puts "errByte:$errByte hexVal:$hexVal errText:$errText" + # convert to decimal for calculations + set decVal [expr 0x$hexVal] + #puts "decErrByte:$decErrByte decVal:$decVal errText:$errText" + if { $decErrByte >= $decVal } { + if {[string length $errorText] > 1} { + set errorText "$errorText$delim$errText" + } else { + set errorText $errText + } + set decErrByte [expr $decErrByte - $decVal] + } + } + } message ] { + return -code error "in decodeErrByte(): $message. errByte: $errByte, errList: $errList" + } + if {2 > [string length $errText]} { + set $errorText "Unknown error" + } + return $errorText +} + +## +# @brief extracts one byte from the 4-byte status string, each byte is +# represented by a two digit hexadecimal number. +# @param statusByteString The string holding the 4 status bytes +# @return returnval String holding the analysed status information +proc extractStatusByte {statusByteString whichByte} { +# A typical argument may look like this: +# "STA/0000C100" + if [ catch { + set statusByte "UNKNOWN" + set statusByteStringLength [string length $statusByteString] + set offset [expr $whichByte*2] + # allow for an initial offset of 4 char for 'STA/' + incr offset 4 + set statusByte [string range $statusByteString $offset [expr $offset+1]] + #puts "ExtractValue(): response:$response, extractedValue:$extractedValue" + } message ] { + return -code error "in extractStatusByte(): $message. statusByteString: $statusByteString, whichByte: $whichByte" + } + return $statusByte +} + + + +proc analyseStatusByte {statusByteString} { +# A typical argument may look like this: +# "STA/0000C100" + if [ catch { + #puts "statusByteString:$statusByteString" + set LSB_ErrByteTxt "Ok" + set MSB_ErrByteTxt "Ok" + set PwrSupplyStatusByteTxt "Ok" + set StateMachineStatusByteTxt "Ok" + set tmp_LSB_ErrByte [extractStatusByte $statusByteString 0] + set tmp_MSB_ErrByte [extractStatusByte $statusByteString 1] + set tmp_PwrSupplyStatusByte [extractStatusByte $statusByteString 2] + set tmp_StateMachineStatusByte [extractStatusByte $statusByteString 3] + if { 0 != [string compare -length 2 $tmp_LSB_ErrByte "00"]} { + # Error byte, LSB + # Value, hex, Explanation + set errList {\ +08 {External Security}\ +04 {Temperature transformer, rectifier or PWM}\ +02 {Line Phase missing} +01 {Cooling water failure} + } + set LSB_ErrByteTxt [decodeErrByte $tmp_LSB_ErrByte $errList] + } + + if { 0 != [string compare -length 2 $tmp_MSB_ErrByte "00"]} { + # status byte hex Value, Explanation + set errList {\ +08 {Inrush procedure error}\ +01 {Current limit exceeded} + } + set MSB_ErrByteTxt [decodeErrByte $tmp_MSB_ErrByte $errList] + } + + if { 0 != [string compare -length 2 $tmp_PwrSupplyStatusByte "00"]} { + # status byte hex Value, Explanation + set errList {\ +80 {Remote enabled}\ +40 {DC power on}\ +20 {Normal polarity}\ +10 {Reverse polarity}\ +04 {External reference enabled}\ +02 {Field regulation enabled}\ +01 {Internal reference set} + } + set PwrSupplyStatusByteTxt [decodeErrByte $tmp_PwrSupplyStatusByte $errList] + } + + if { 0 != [string compare -length 2 $tmp_StateMachineStatusByte "00"]} { + # status byte hex Value; State + set errList {\ +1C {Return to neutral state}\ +1B {Control DC interlock}\ +1A {Wait time}\ +19 {Set time 1 second}\ +18 {Set Inrush OFF}\ +17 {Wait time}\ +16 {Set time 1 second}\ +15 {Set DC ON}\ +14 {Wait time}\ +13 {Set time 1 second}\ +12 {Set Inrush ON}\ +11 {Test ADC:0}\ +10 {Set DAC to 0}\ +0F {Set reference to intern}\ +0C {Return to neutral state}\ +0B {Wait time}\ +0A {Set 2 seconds}\ +09 {Set reference to intern}\ +08 {Set DC off}\ +07 {Test ADC:0}\ +06 {Test DAC:0}\ +05 {Ramp DAC to 0} + } + #00 {Neutral state} + set StateMachineStatusByteTxt [decodeErrByte $tmp_StateMachineStatusByte $errList] + } + #puts "LSB_ErrByte :x$tmp_LSB_ErrByte: $LSB_ErrByteTxt" + #puts "MSB_ErrByte :x$tmp_MSB_ErrByte: $MSB_ErrByteTxt" + #puts "PwrSupplyStatus:x$tmp_PwrSupplyStatusByte: $PwrSupplyStatusByteTxt" + #puts "StateMachine :x$tmp_StateMachineStatusByte: $StateMachineStatusTxt" + + hset $::scobj::bruker_BEC1::bruker_BEC1_path2nodes/pwrCtrl/LSB_Err "x$tmp_LSB_ErrByte: $LSB_ErrByteTxt" + hset $::scobj::bruker_BEC1::bruker_BEC1_path2nodes/pwrCtrl/MSB_Err "x$tmp_MSB_ErrByte: $MSB_ErrByteTxt" + hset $::scobj::bruker_BEC1::bruker_BEC1_path2nodes/pwrCtrl/PwrSupplyStatus "x$tmp_PwrSupplyStatusByte: $PwrSupplyStatusByteTxt" + hset $::scobj::bruker_BEC1::bruker_BEC1_path2nodes/pwrCtrl/StateMachineStatus "x$tmp_StateMachineStatusByte: $StateMachineStatusByteTxt" + } message ] { + return -code error "in analyseStatusByte(): $message. statusByteString: $statusByteString" + } + return "Ok" +} + + + +## +# @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 Checktol() checks whether the current field strength or current is within tolerance. +# @param tc_root string variable holding the path to the object's base node in sics +# @return retVal returns 1 if in tolerance, 0 else. +proc checktol {tc_root} { + if [ catch { + set retVal 0 + set sensorValue $tc_root/sensor/DesiredCurrent + set NominalOutpCurrent [hval $sensorValue] + set isetp $tc_root/sensor/NominalOutpCurrent + set setpt [hval $isetp] + set tol [hval $tc_root/emon/tolerance] + set loField [expr $setpt - $tol] + set hiField [expr $setpt + $tol] + if { $NominalOutpCurrent < $loField || $NominalOutpCurrent > $hiField} { + hset $tc_root/emon/isInTolerance "outsideTolerance" + set retVal 0 + } else { + hset $tc_root/emon/isInTolerance "inTolerance" + set retVal 1 + } + } message ] { + return -code error "in checktol: $message. Last query command: $::scobj::bruker_BEC1::bruker_BEC1_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 +# @return 'OK' if the new setpoint is within the alarm levels; else an error is reported +proc check {tc_root whichParameter} { + # Unlike the Lakeshore temperature controllers, the BEC1 does not manage alarm levels. + # If the chosen setpoint is outside what is permitted, an error will be set. + # Nonetheless the drivable interface requires this check() routine - so let's + # give it one that is always happy... + 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. +# The help text provided is taken from the user manual - it is as good or bad +# as the manual +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 { + "LocalRemoteState*" { + set h1 {REM: «n» Query or Set local/remote state.} + set h2 {«n» 0:local (local and remote comands allowed).} + set h3 {«n» 1:remote state (only remote commands allowed).} + set helptext $h1$CRLF$h2$CRLF$h3 + } + "DC_power*" { + set h1 {DCP «off/on» DC power } + set h2 { 0:off, 1:on} + set helptext $h1$CRLF$h2 + } + "DesiredCurrent*" { + set h1 {CUR «nn.nn» Read and set DAC current} + set h2 {«nn.nn» DAC current in Amperes} + set helptext $h1$CRLF$h2 + } + "PwrCtrlFrom*" { + set h1 {EXT: «n» Query / Set source of reference for power supply control} + set h2 {«EXT:0» internal reference, DAC} + set h3 {«EXT:1» external reference voltage} + set h4 {«EXT:2» external reference field} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4 + } + "QueryPolarity*" { + set h1 {POL/ Query polarity} + set h2 {«POL:0» no polarity reversal unit available} + set h3 {«POL:1» positive polarity} + set h4 {«POL:2» negative polarity} + set h5 {«POL:3» polarity reversal unit busy - please wait} + set helptext $h1$CRLF$h2$CRLF$h3$CRLF$h4$CRLF$h5 + } + "SetPolarity*" { + set h1 {POL:«n» Set polarity} + set h2 {«POL:0» Set positive polarity} + set h3 {«POL:1» Set negative polarity} + set helptext $h1$CRLF$h2$CRLF$h3 + } + "ActualDAC_Field*" { + set h1 {FLD «nn.nn» Query/Set magnetic field strength } + set h2 {«nn.nn» magnetic field strength in Teslas (setpoint)} + set helptext $h1$CRLF$h2 + } + "NominalOutpCurrent*" { + set h1 {CHN «nn.nn» Query nominal output current.} + set h2 {«nn.nn» Nominal output current in Amperes} + set helptext $h1$CRLF$h2 + } + "OutputVoltage*" { + set h1 {CHV «nn.nn» Query output voltage.} + set h2 { «nn.nn» Output voltage in Volts.} + set helptext $h1$CRLF$h2 + } + "LoadResistance*" { + set h1 {RES/ Query load resistance } + set h2 {Returned: Load resistance in Ohms.} + set helptext $h1$CRLF$h2 + } + "MeasuredField*" { + set h1 {CHF/ Query measured magnetic field. } + set h2 {Returned: Measured magnetic field in Teslas.} + set h3 {} + set helptext $h1$CRLF$h2$CRLF$h3 + } + "ResetErrorMsg*" { + set h1 {RST: «0» Reset error messages. } + set h2 { «0» Send zero to reset error messages.} + set helptext $h1$CRLF$h2 + } + "statusByte*" { + set h1 {STA/ Query status information about the power supply or STA:0 Reset the command flow. } + set h2 {Returned: status byte in hexadecimal format. Note: DC power must be ON} + set helptext $h1$CRLF$h2 + } + "LSB_Err*" { + set h1 {Interpretation of the first 2 hexadecimal characters of the StatusByte string.} + set h2 {LSB (lower status byte) contains interlock status conditions} + set h3 {such as water or partial power failure.} + set helptext $h1$CRLF$h2$CRLF$h3 + } + "MSB_Err*" { + set h1 {Interpretation of the hexadecimal characters 3 and 4 of the StatusByte string.} + set h2 {MSB (machine status byte) contains interlock status conditions} + set h3 {such as an overcurrent condition.} + set helptext $h1$CRLF$h2$CRLF$h3 + } + "PwrSupplyStatus*" { + set h1 {Interpretation of the hexadecimal characters 5 and 6 of the StatusByte string.} + set h2 {Contains information about the power supply status} + set h3 {such as DC power on or off.} + set helptext $h1$CRLF$h2$CRLF$h3 + } + "StateMachineStatus*" { + set h1 {Interpretation of the hexadecimal characters 7 and 8 of the StatusByte string.} + set h2 {Contains information about the power supply State Machines} + set h3 {such as the Inrush or Test DAC states.} + set helptext $h1$CRLF$h2$CRLF$h3 + } + "status*" { + set helptext {Device status. Is 'idle' while in tolerance or 'busy' while driving or resetting} + } + "EthernetAddr*" { + set h1 {ETH/ Query ethernet address of the power supply } + set h2 {Returned: ethernet address in hexadecimal numbers.} + set helptext $h1$CRLF$h2 + } + "isInTolerance*" { + set h1 {A flag that indicates whether the actual magnetic field strength is within tolerance} + set h2 {of the setpoint field strength.} + set helptext $h1$CRLF$h2 + } + "apply_tolerance*" { + set h1 {A flag that indicates whether the power control should actively try} + set h2 {to keep the magnetic field strength within the set tolerance of the setpoint.} + set helptext $h1$CRLF$h2 + } + "tolerance*" { + set h1 {Tolerance specifies the magnetic field strength tolerance in Tesla applicable to the setpoint.} + set helptext $h1 + } + "monMode*" { + set h1 {A flag that indicates whether the magnet's current is being actively changed to} + set h2 {drive the magnetic field towards the setpoint or whether the current is stable} + set h3 {with the magnetic field 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 + } + "lastErrorMsg*" { + set helptext {lastErrorMsg and lastErrorMsg2 display the last 2 error messages received from the device.} + } + 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 bruker_BEC1 scriptcontext object (typically sct_bruker_BEC1_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 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 klass Nexus class name (?) +# @return OK +proc createNode {scobj_hpath sct_controller cmdGroup varName readable writable\ + pollEnabled drivable replyLen dataType permission rdCmd rdFunc wrCmd\ + wrFunc allowedValues klass} { + #puts "createing node for: $scobj_hpath $cmdGroup $varName $readable $writable $pollEnabled $drivable $dataType $permission $rdCmd $rdFunc $wrCmd $wrFunc" + if [ catch { + set ns ::scobj::bruker_BEC1 + 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 $replyLen + } + if {$pollEnabled == 1} { + # puts "enabling polling for $nodeName" + $sct_controller poll $nodeName + } + hsetprop $nodeName $rdFunc ${ns}::$rdFunc $replyLen + if {$writable == 1} { + hsetprop $nodeName write ${ns}::$wrFunc $scobj_hpath noResponse $wrCmd + 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 + hsetprop $nodeName checkstatus ${ns}::drivestatus $scobj_hpath + hsetprop $nodeName halt ${ns}::halt $scobj_hpath + } + } message ] { + return -code error "in createNode $message" + } + helpNotes4user $scobj_hpath $cmdGroup $varName + return OK +} + + +## +# @brief mk_sct_bruker_BEC1() creates a scriptcontext object for a Bruker BEC1 power supply (for 1T magnet) +# @param sct_controller name of the bruker_BEC1 scriptcontext object (typically sct_bruker_BEC1_tc1 or tc2) +# @param klass Nexus class name (?), typically 'environment' +# @param tempobj short name for the magnet power supply scriptcontext object (typ. ma1 or ma2) +# @param tol magentic field strength tolerance in Tesla (typ. 1) +# @return nothing (well, the sct object) +proc mk_sct_bruker_BEC1 {sct_controller klass tempobj tol} { + if [ catch { + set ns ::scobj::bruker_BEC1 + set ::scobj::bruker_BEC1::bruker_BEC1_sct_obj_name $tempobj + + # terminator string for serial communication + set CR "\r" + set LF "\n" + #set ::scobj::bruker_BEC1::bruker_BEC1_term $CR + set ::scobj::bruker_BEC1::bruker_BEC1_term "" + + set ::scobj::bruker_BEC1::bruker_BEC1_tolerance $tol + set ::scobj::bruker_BEC1::bruker_BEC1_driveTolerance [expr $tol * $::scobj::bruker_BEC1::bruker_BEC1_tolerance] + + MakeSICSObj $tempobj SCT_OBJECT + sicslist setatt $tempobj klass $klass + 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::bruker_BEC1::bruker_BEC1_path2nodes $scobj_hpath + + # Create state machines for the following device commands (non-polled entries are place-holders + # for manually maintained nodes like parameters in human-readable form) + # 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 + # replyLen + # 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 MeasuredField 1 0 1 0 13 float spy {CHF/} {rdValue} {} {setValue} {}\ + sensor ActualDAC_Field 1 1 1 0 16 float user {FLD/} {rdValue} {FLD=} {setValue} {}\ + sensor DesiredCurrent 1 1 1 1 15 float user {CUR/} {rdValue} {CUR=} {setDesiredCurrent} {}\ + sensor NominalOutpCurrent 1 0 1 0 13 float spy {CHN/} {rdValue} {} {setDesiredCurrent} {}\ + } + set deviceCommand {\ + pwrCtrl DC_power 1 1 1 0 7 int user {DCP/} {inTolerance} {DCP=} {setValue} {0,1}\ + pwrCtrl PwrCtrlFrom 1 1 1 0 7 int user {EXT/} {rdValue} {EXT=} {setValue} {0,1,2}\ + pwrCtrl LocalRemoteState 1 1 1 0 7 int user {REM/} {rdValue} {REM=} {setValue} {0,1}\ + pwrCtrl OutputVoltage 1 0 1 0 12 float spy {CHV/} {rdValue} {} {setValue} {}\ + pwrCtrl ResetErrorMsg 0 1 0 0 7 int user {} {rdValue} {RST=} {setValue} {0}\ + pwrCtrl statusByte 1 1 1 0 13 text user {STA/} {rdValue} {STA=} {setValue} {0}\ + } + # it seems these functions are not supported by our device + #pwrCtrl EthernetAddrHex 1 0 1 0 text spy {ETH/} {rdValue} {} {setValue} {}\ + #pwrCtrl LoadResistance 1 0 1 0 float spy {RES/} {rdValue} {} {setValue} {}\ + #pwrCtrl QueryPolarity 1 0 1 0 int spy {POL/} {rdValue} {} {setValue} {}\ + #pwrCtrl SetPolarity 0 1 0 0 int user {} {rdValue} {POL=} {setValue} {0,1}\ + + hfactory $scobj_hpath/sensor plain spy none + foreach {cmdGroup varName readable writable pollEnabled drivable replyLen dataType permission rdCmd rdFunc wrCmd wrFunc allowedValues} $deviceCommandToplevel { + createNode $scobj_hpath $sct_controller $cmdGroup $varName $readable $writable $pollEnabled $drivable $replyLen $dataType $permission $rdCmd $rdFunc $wrCmd $wrFunc $allowedValues $klass + } + + # create a base node for each commandGroup element - these are all polled + hfactory $scobj_hpath/emon plain spy none + hfactory $scobj_hpath/pwrCtrl plain spy none + + foreach {cmdGroup varName readable writable pollEnabled drivable replyLen dataType permission rdCmd rdFunc wrCmd wrFunc allowedValues} $deviceCommand { + createNode $scobj_hpath $sct_controller $cmdGroup $varName $readable $writable $pollEnabled $drivable $replyLen $dataType $permission $rdCmd $rdFunc $wrCmd $wrFunc $allowedValues $klass + } + + # Create state machines for the following required nodes that do not correspond + # to device commands. + hfactory $scobj_hpath/emon/lastErrorMsg plain user text + #hsetprop $scobj_hpath/emon/lastErrorMsg values *any* + hset $scobj_hpath/emon/lastErrorMsg $::scobj::bruker_BEC1::bruker_BEC1_errMsg + helpNotes4user $scobj_hpath "emon" "lastErrorMsg" + hfactory $scobj_hpath/emon/lastErrorMsg2 plain user text + hset $scobj_hpath/emon/lastErrorMsg2 $::scobj::bruker_BEC1::bruker_BEC1_errMsg2 + helpNotes4user $scobj_hpath "emon" "lastErrorMsg2" + + #hfactory $scobj_hpath/emon/statusText plain user text + #hset $scobj_hpath/emon/statusText $::scobj::bruker_BEC1::bruker_BEC1_statusText + #helpNotes4user $scobj_hpath "emon" "statusText" + + hfactory $scobj_hpath/emon/apply_tolerance plain user int + hsetprop $scobj_hpath/emon/apply_tolerance values 0,1 + hset $scobj_hpath/emon/apply_tolerance 1 + helpNotes4user $scobj_hpath "emon" "apply_tolerance" + + hfactory $scobj_hpath/emon/isInTolerance plain spy text + hsetprop $scobj_hpath/emon/isInTolerance values idle,drive,monitor,error + hset $scobj_hpath/emon/isInTolerance "inTolerance" + helpNotes4user $scobj_hpath "emon" "isInTolerance" + + hfactory $scobj_hpath/emon/tolerance plain user float + hsetprop $scobj_hpath/emon/tolerance units $::scobj::bruker_BEC1::bruker_BEC1_magneticFiledUnits + # hsetprop $scobj_hpath/emon/tolerance units "T" + hset $scobj_hpath/emon/tolerance $tol + helpNotes4user $scobj_hpath "emon" "tolerance" + + # environment monitoring flags: shows if setpoints (field, current) are in tolerance + hfactory $scobj_hpath/emon/monMode plain user text + hsetprop $scobj_hpath/emon/monMode values idle,drive,monitor,error + hset $scobj_hpath/emon/monMode "idle" + helpNotes4user $scobj_hpath "emon" "monMode" + + hfactory $scobj_hpath/emon/errhandler plain spy text + hset $scobj_hpath/emon/errhandler "pause" + helpNotes4user $scobj_hpath "emon" "errhandler" + + hfactory $scobj_hpath/pwrCtrl/LSB_Err plain spy text + hset $scobj_hpath/pwrCtrl/LSB_Err "UNKNOWN" + helpNotes4user $scobj_hpath "pwrCtrl" "LSB_Err" + + hfactory $scobj_hpath/pwrCtrl/MSB_Err plain spy text + hset $scobj_hpath/pwrCtrl/MSB_Err "UNKNOWN" + helpNotes4user $scobj_hpath "pwrCtrl" "MSB_Err" + + hfactory $scobj_hpath/pwrCtrl/PwrSupplyStatus plain spy text + hset $scobj_hpath/pwrCtrl/PwrSupplyStatus "UNKNOWN" + helpNotes4user $scobj_hpath "pwrCtrl" "PwrSupplyStatus" + + hfactory $scobj_hpath/pwrCtrl/StateMachineStatus plain spy text + hset $scobj_hpath/pwrCtrl/StateMachineStatus "UNKNOWN" + helpNotes4user $scobj_hpath "pwrCtrl" "StateMachineStatus" + + #hfactory $scobj_hpath/pwrCtrl/EthernetAddrDec plain user text + #hset $scobj_hpath/pwrCtrl/EthernetAddrDec "UNKNOWN" + #helpNotes4user $scobj_hpath "pwrCtrl" "EthernetAddrDec" + + hfactory $scobj_hpath/status plain spy text + hsetprop $scobj_hpath/status values busy,idle + hset $scobj_hpath/status "idle" + helpNotes4user $scobj_hpath "" "status" + + ::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/MeasuredField sensor user + $scobj_hpath sensor/ActualDAC_Field sensor user + $scobj_hpath sensor/DesiredCurrent sensor user + $scobj_hpath sensor/NominalOutpCurrent sensor user + " + set aliasProperties " + $scobj_hpath MeasuredField sensor _sensor_MeasuredField + $scobj_hpath ActualDAC_Field sensor _sensor_ActualDAC_Field + $scobj_hpath DesiredCurrent sensor _sensor_DesiredCurrent + $scobj_hpath NominalOutpCurrent sensor _sensor_NominalOutpCurrent + " + foreach {rootpath hpath klass priv} $nxProperties { + hsetprop $rootpath/$hpath klass $klass + 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) + ::scobj::hinitprops $tempobj/sensor NominalOutpCurrent + + ansto_makesctdrive ${tempobj}_driveable $scobj_hpath/sensor/DesiredCurrent $scobj_hpath/sensor/NominalOutpCurrent $sct_controller + + # initialise the device + bruker_BEC1_init $sct_controller $scobj_hpath + puts "Bruker BEC1 power supply for 1-Tesla magnet ready at /sample/$tempobj (Driver 20091002_beta)" + } message ] { + return -code error "in mk_sct_bruker_BEC1 $message" + } + } + namespace export mk_sct_bruker_BEC1 + # endproc mk_sct_bruker_BEC1 sct_controller klass tempobj tol bruker_BEC1_LSmodel +} +# end of namespace mk_sct_bruker_BEC1 + +## +# @brief add_bruker_BEC1() adds a scriptcontext object for a Bruker BEC1 power supply (1T magnet) +# and makes it available to SICServer +# @param name short name for the temperature controller scriptcontext object (typ. ma1 or ma2) +# @param IP IP address of the device (e.g. IP of moxabox that hooks up to the BEC1) +# @param port port number on the moxabox (typ. 4001, 4002, 4003, or 4004) +# @param tol magnetic field strength tolerance in Tesla (default 0.1T) +# @return nothing (well, the sct object) +proc add_bruker_BEC1 {name IP port {_tol 0.1} } { + if [ catch { + puts "\nadd_bruker_BEC1: makesctcontroller $name std ${IP}:$port for Bruker BEC1 1-Tesla magnet power supply" + makesctcontroller sct_bruker_BEC1_$name std ${IP}:$port "\r" + mk_sct_bruker_BEC1 sct_bruker_BEC1_$name environment $name $_tol + makesctemon $name /sics/$name/emon/monMode /sics/$name/emon/isInTolerance /sics/$name/emon/errhandler + } message ] { + return -code error "in add_bruker_BEC1: $message" + } +} + +namespace import ::scobj::bruker_BEC1::* diff --git a/site_ansto/instrument/config/environment/temperature/sct_lakeshore_3xx.tcl b/site_ansto/instrument/config/environment/temperature/sct_lakeshore_3xx.tcl new file mode 100755 index 00000000..1897fe23 --- /dev/null +++ b/site_ansto/instrument/config/environment/temperature/sct_lakeshore_3xx.tcl @@ -0,0 +1,2049 @@ +# 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: 20091127 for sics2_4 +# +# known bugs/limitations: beta stage, lacks testing of ramping, relays, setpoint driving, +# 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. +# ----------------------------------------------------------------------------*/ + +# 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::ls3xx { + # 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 ls3xx_sct_obj_name "UNKNOWN" + # What Lakeshore model are we dealing with? 336 or 340? + set ls3xx_LSmodel "UNKNOWN" + # Last query and write command sent to the device - for debugging and other tasks + set ls3xx_lastQueryCmd " " + set ls3xx_lastWriteCmd " " + # provide a global variable holding the path to the nodes + set ls3xx_path2nodes "/sample/tc1" + # terminator string for serial communication (empty for ls340, taken car of with the COMM command) + # obsolete - handled by sct object - set ls3xx_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 ls3xx_driveTolerance 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 5 + # default Heater Range (0,..,5) zero is off, hence the least dangerous + set ls3xx_range 0 + # upper and lower temperature limit in Kelvin + set ls3xx_upperlimit 500.0 + set ls3xx_lowerlimit 4.0 + # temperature units are Kelvin + set ls3xx_tempUnits "K" + # ls3xx status byte + set ls3xx_statusByte -1 + # 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 ls3xx_inputStatusA "UNKNOWN" + set ls3xx_inputStatusB "UNKNOWN" + set ls3xx_inputStatusC "UNKNOWN" + set ls3xx_inputStatusD "UNKNOWN" + set ls3xx_inpSetupA "UNKNOWN" + set ls3xx_inpSetupB "UNKNOWN" + set ls3xx_inpSetupC "UNKNOWN" + set ls3xx_inpSetupD "UNKNOWN" + set ls3xx_input4CtrlLp1 "0" + set ls3xx_input4CtrlLp2 "0" + set ls3xx_input4CtrlLp3 "0" + set ls3xx_input4CtrlLp4 "0" + set ls3xx_sampleSensor "UNKNOWN" + set timeInSecsSince2000 0 + + set alarm_Limit_LoA $ls3xx_lowerlimit + set alarm_Limit_LoB $ls3xx_lowerlimit + set alarm_Limit_LoC $ls3xx_lowerlimit + set alarm_Limit_LoD $ls3xx_lowerlimit + set alarm_Limit_HiA $ls3xx_upperlimit + set alarm_Limit_HiB $ls3xx_upperlimit + set alarm_Limit_HiC $ls3xx_upperlimit + set alarm_Limit_HiD $ls3xx_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 ls3xx: +# 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 ls3xx_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 + set ::scobj::ls3xx::timeInSecsSince2000 [clock scan 20000101T000000A] + # set the communication protocol: terminator , bps 9600 baud, 7 data bits + + # 1 stop bit + odd parity bit + if { $::scobj::ls3xx::ls3xx_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::ls3xx::this_sDeviceID data +# puts "sct_lakeshore3xx.tcl: connected to device $::scobj::ls3xx::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::ls3xx::ls3xx_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::ls3xx::this_selfTestResult [hval $tc_root/other/selftest] + if {$::scobj::ls3xx::this_selfTestResult == 0} { + puts "sct_lakeshore3xx.tcl: Lakeshore $::scobj::ls3xx::ls3xx_LSmodel self-test ok." + } else { + puts "sct_lakeshore3xx.tcl: The Lakeshore $::scobj::ls3xx::ls3xx_LSmodel failed its self-test." + } + # Set the default upper and lower temperature alarm limits in Kelvin + foreach iSensor $::scobj::ls3xx::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::ls3xx::ls3xx_LSmodel == 340 } { +# hset $tc_root/heater/heaterRange $::scobj::ls3xx::ls3xx_range + } else { +# hset $tc_root/heater/heaterRange_1 $::scobj::ls3xx::ls3xx_range +# hset $tc_root/heater/heaterRange_2 $::scobj::ls3xx::ls3xx_range + } + # Set the default settle parameters + if { $::scobj::ls3xx::ls3xx_LSmodel == 340 } { + hset $tc_root/control/settleThr_Loop_1 $::scobj::ls3xx::ls340_settleThr + hset $tc_root/control/settleTime_Loop_1 $::scobj::ls3xx::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/tolerance $::scobj::ls3xx::ls3xx_driveTolerance + } message ] { + return -code error "in ls3xx_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 { 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::ls3xx::ls3xx_lastQueryCmd $cmd + #sct send $cmd$::scobj::ls3xx::ls3xx_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::ls3xx::ls3xx_lastQueryCmd "$cmd$whichCurve" + sct send $cmd$whichCurve + } else { + set ::scobj::ls3xx::ls3xx_lastQueryCmd "$cmd" + sct send $cmd + } + } message ] { + return -code error "in getValue: $message. Last query command: $::scobj::ls3xx::ls3xx_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::ls3xx::ls3xx_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::ls3xx::ls3xx_path2nodes/input/calCurveHdr_$idx + hset $nodename "noCurve" + } + } + # Continue as normal + switch -glob -- $data { + "ASCERR:*" { + puts "ASCERR in rdValue: Last query command: $::scobj::ls3xx::ls3xx_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::ls3xx::ls3xx_LSmodel "336"] \ + && 0 == [string compare -length 7 $::scobj::ls3xx::ls3xx_lastQueryCmd "INTYPE?"] } { + set mustUpdate 1 + } + if {0 == [string compare -length 6 $::scobj::ls3xx::ls3xx_lastQueryCmd "INSET?"] || $mustUpdate == 1 } { + switch $idx { + "A" {set ::scobj::ls3xx::ls3xx_inpSetupA $data} + "B" {set ::scobj::ls3xx::ls3xx_inpSetupB $data} + "C" {set ::scobj::ls3xx::ls3xx_inpSetupC $data} + "D" {set ::scobj::ls3xx::ls3xx_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::ls3xx::ls3xx_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::ls3xx::getValFromString $data 0 $separator] + set aday [::scobj::ls3xx::getValFromString $data 1 $separator] + set ayear [::scobj::ls3xx::getValFromString $data 2 $separator] + set ahour [::scobj::ls3xx::getValFromString $data 3 $separator] + set amin [::scobj::ls3xx::getValFromString $data 4 $separator] + set asec [::scobj::ls3xx::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::ls3xx::timeInSecsSince2000] + hset $::scobj::ls3xx::ls3xx_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::ls3xx::ls3xx_path2nodes/sensor/timStamp $isoTimeString + } + } + } + } + } + } message ] { + return -code error "in rdValue: $message. Last query command: $::scobj::ls3xx::ls3xx_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::ls3xx::ls3xx_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::ls3xx::getValFromString $data 0 $separator] + if { $::scobj::ls3xx::ls3xx_LSmodel == 340 } { + #set source [::scobj::ls3xx::getValFromString $data 1 $separator] + set hiVal [::scobj::ls3xx::getValFromString $data 2 $separator] + set loVal [::scobj::ls3xx::getValFromString $data 3 $separator] + #set latch [::scobj::ls3xx::getValFromString $data 4 $separator] + #set relay [::scobj::ls3xx::getValFromString $data 5 $separator] + } else { + set hiVal [::scobj::ls3xx::getValFromString $data 1 $separator] + set loVal [::scobj::ls3xx::getValFromString $data 2 $separator] + #set deadbd [::scobj::ls3xx::getValFromString $data 3 $separator] + #set latch [::scobj::ls3xx::getValFromString $data 4 $separator] + #set audible [::scobj::ls3xx::getValFromString $data 5 $separator] + #set visible [::scobj::ls3xx::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::ls3xx::checkAlarmLimitsA $onOff + if {$loVal >= 0} {set ::scobj::ls3xx::alarm_Limit_LoA $loVal} + if {$hiVal >= 0} {set ::scobj::ls3xx::alarm_Limit_HiA $hiVal} + } + "B" { + set ::scobj::ls3xx::checkAlarmLimitsB $onOff + if {$loVal >= 0} {set ::scobj::ls3xx::alarm_Limit_LoB $loVal} + if {$hiVal >= 0} {set ::scobj::ls3xx::alarm_Limit_HiB $hiVal} + } + "C" { + set ::scobj::ls3xx::checkAlarmLimitsC $onOff + if {$loVal >= 0} {set ::scobj::ls3xx::alarm_Limit_LoC $loVal} + if {$hiVal >= 0} {set ::scobj::ls3xx::alarm_Limit_HiC $hiVal} + } + "D" { + set ::scobj::ls3xx::checkAlarmLimitsD $onOff + if {$loVal >= 0} {set ::scobj::ls3xx::alarm_Limit_LoD $loVal} + if {$hiVal >= 0} {set ::scobj::ls3xx::alarm_Limit_HiD $hiVal} + } + } + } + } + } + } message ] { + return -code error "in rdAlarmVal: $message. Last query command: $::scobj::ls3xx::ls3xx_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::ls3xx::ls3xx_lastQueryCmd" + sct geterror $data + } + default { + set oval [sct oldval] + if { $data != [sct oldval] } { + # puts "rdCfgValue: idx:$idx Last query command: $::scobj::ls3xx::ls3xx_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::ls3xx::ls3xx_LSmodel == 340 } { + # LS340, device command CSET* + set input [::scobj::ls3xx::getValFromString $data 0 $separator] + set units [::scobj::ls3xx::getValFromString $data 1 $separator] + set onOff [::scobj::ls3xx::getValFromString $data 2 $separator] + #set powerup [::scobj::ls3xx::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::ls3xx::getValFromString $data 0 $separator] + set input [::scobj::ls3xx::getValFromString $data 1 $separator] + # set powerup [::scobj::ls3xx::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 ls3xx_input4CtrlLp idx:$idx, input:$input, myInp:$myInp" + } + set nodename $::scobj::ls3xx::ls3xx_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::ls3xx::ls3xx_input4CtrlLp1 $input} + "2" {set ::scobj::ls3xx::ls3xx_input4CtrlLp2 $input} + "3" {set ::scobj::ls3xx::ls3xx_input4CtrlLp3 $input} + "4" {set ::scobj::ls3xx::ls3xx_input4CtrlLp4 $input} + } + # puts "rdCfgValue ls3xx_input4CtrlLp:$::scobj::ls3xx::ls3xx_input4CtrlLp2 idx:$idx, input:$input, myInp:$myInp" + } + } + } + } message ] { + return -code error "in rdCfgValue: $message. Last query command: $::scobj::ls3xx::ls3xx_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::ls3xx::ls3xx_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::ls3xx::ls3xx_inputStatusA $sValue} + "B" {set ::scobj::ls3xx::ls3xx_inputStatusB $sValue} + "C" {set ::scobj::ls3xx::ls3xx_inputStatusC $sValue} + "D" {set ::scobj::ls3xx::ls3xx_inputStatusD $sValue} + } + } + } + } + } message ] { + return -code error "in rdBitValue: $message. Last query command: $::scobj::ls3xx::ls3xx_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::ls3xx::ls3xx_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::ls3xx::ls3xx_path2nodes/sensor/sampleSensor + set ::scobj::ls3xx::ls3xx_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::ls3xx::ls3xx_inputStatusA + set inpEnabled $::scobj::ls3xx::ls3xx_inpSetupA + switch $idx { + "B" {set inpEnabled $::scobj::ls3xx::ls3xx_inpSetupB} + "C" {set inpEnabled $::scobj::ls3xx::ls3xx_inpSetupC} + "D" {set inpEnabled $::scobj::ls3xx::ls3xx_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::ls3xx::ls3xx_inputStatusA} + "B" {set inputStatus $::scobj::ls3xx::ls3xx_inputStatusB} + "C" {set inputStatus $::scobj::ls3xx::ls3xx_inputStatusC} + "D" {set inputStatus $::scobj::ls3xx::ls3xx_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::ls3xx::ls3xx_sampleSensor} { + hset $::scobj::ls3xx::ls3xx_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::ls3xx::ls3xx_input4CtrlLp1} { + hset $::scobj::ls3xx::ls3xx_path2nodes/sensor/ctrlLp1_value $value + } elseif {$idx == $::scobj::ls3xx::ls3xx_input4CtrlLp2} { + hset $::scobj::ls3xx::ls3xx_path2nodes/sensor/ctrlLp2_value $value + } elseif {$idx == $::scobj::ls3xx::ls3xx_input4CtrlLp3} { + hset $::scobj::ls3xx::ls3xx_path2nodes/sensor/ctrlLp3_value $value + } elseif {$idx == $::scobj::ls3xx::ls3xx_input4CtrlLp4} { + hset $::scobj::ls3xx::ls3xx_path2nodes/sensor/ctrlLp4_value $value + } + } + } + } + } message ] { + return -code error "in rdInpValue: $message. Last query command: $::scobj::ls3xx::ls3xx_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 LS3xx 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::ls3xx::ls3xx_path2nodes + set data [sct result] + set oldval [sct oldval] + # puts "inTolerance(): data=$data oldval=$oldval idx:$CtrlLoopIdx" + switch -glob -- $data { + "ASCERR:*" { + puts "ASCERR in inTolerance: Last query command: $::scobj::ls3xx::ls3xx_lastQueryCmd" + sct geterror $data + } + default { + if {$data != $oldval} { + if {$oldval == "UNKNOWN"} { + sct utime timecheck + } + sct oldval $data + sct update $data + sct utime readtime + } + } + } + # puts "inTolerance $::scobj::ls3xx::ls3xx_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::ls3xx::ls3xx_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} { + set intol [checktol $tc_root [sct readtime] [sct timecheck] $CtrlLoopIdx $iSensor] + if {$intol==0} { sct utime timecheck } + 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::ls3xx::ls3xx_driveTolerance = 0.2 Kelvin + if {$diff > $::scobj::ls3xx::ls3xx_driveTolerance} { + set nodename $tc_root/emon/monMode_Lp$CtrlLoopIdx + hset $nodename "drive" + if {$CtrlLoopIdx == 1} { + hset $tc_root/status "busy" + } else { + 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" + } else { + hset $tc_root/status_Ctrl_Lp2 "idle" + } + } + } + } else { + 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::ls3xx::ls3xx_sct_obj_name CtrlLoopIdx:$CtrlLoopIdx data:$data" + } message ] { + return -code error "in inTolerance: $message. Last query command: $::scobj::ls3xx::ls3xx_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::ls3xx::ls3xx_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::ls3xx::ls3xx_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::ls3xx::ls3xx_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::ls3xx::ls3xx_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::ls3xx::ls3xx_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::ls3xx::ls3xx_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::ls3xx::ls3xx_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::ls3xx::ls3xx_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::ls3xx::ls3xx_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::ls3xx::ls3xx_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::ls3xx::ls3xx_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::ls3xx::ls3xx_LSmodel == 340 } { + if {$CtrlLoopIdx < 1 || $CtrlLoopIdx > 2} { + set iSensor "UNKOWN" + return $iSensor + } + set nodename $::scobj::ls3xx::ls3xx_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::ls3xx::ls3xx_path2nodes/control/outMode_$CtrlLoopIdx + set data [hval $nodename] + set input [::scobj::ls3xx::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/tolerance] + set lotemp [expr $setpt - $tol] + set hitemp [expr $setpt + $tol] + # puts "checktol: lotemp=$lotemp, temp=$temp, hitemp=$hitemp, tol=$tol" + if { $temp < $lotemp || $temp > $hitemp} { + hset $tc_root/emon/isInTolerance_Lp$iLoop "outsideTolerance" + set retVal 0 + } else { + set timeout $::scobj::ls3xx::ls340_settleTime + if { $::scobj::ls3xx::ls3xx_LSmodel == 340 } { + set timeout [hval $tc_root/control/settleTime_Loop_1] + } + set elapsedTime [expr abs($currtime - $timecheck)] + #puts "if (elapsedTime=$elapsedTime > timeout=$timeout) we are inTolerance" + if {$elapsedTime > $timeout} { + hset $tc_root/emon/isInTolerance_Lp$iLoop "inTolerance" + } + set retVal 1 + } + } message ] { + return -code error "in checktol: $message. Last query command: $::scobj::ls3xx::ls3xx_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::ls3xx::alarm_Limit_LoA + set hilimit $::scobj::ls3xx::alarm_Limit_HiA + set bCheckLimits $::scobj::ls3xx::checkAlarmLimitsA + switch $whichSensor { + "A" { + set lolimit $::scobj::ls3xx::alarm_Limit_LoA + set hilimit $::scobj::ls3xx::alarm_Limit_HiA + set bCheckLimits $::scobj::ls3xx::checkAlarmLimitsA + } + "B" { + set lolimit $::scobj::ls3xx::alarm_Limit_LoB + set hilimit $::scobj::ls3xx::alarm_Limit_HiB + set bCheckLimits $::scobj::ls3xx::checkAlarmLimitsB + } + "C" { + set lolimit $::scobj::ls3xx::alarm_Limit_LoC + set hilimit $::scobj::ls3xx::alarm_Limit_HiC + set bCheckLimits $::scobj::ls3xx::checkAlarmLimitsC + } + "D" { + set lolimit $::scobj::ls3xx::alarm_Limit_LoD + set hilimit $::scobj::ls3xx::alarm_Limit_HiD + set bCheckLimits $::scobj::ls3xx::checkAlarmLimitsD + } + default { + error "sct_ls3xx.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_ls3xx.tcl: setpoint $tc_root/sensor/sensorValue$whichSensor violates set alarm limits" + } + } + } message ] { + return -code error "in check(): $message. Last write command: $::scobj::ls3xx::ls3xx_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::ls3xx::ls3xx_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::ls3xx::ls3xx_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::ls3xx::ls3xx_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::ls3xx::ls3xx_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::ls3xx::ls3xx_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::ls3xx::ls3xx_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::ls3xx::ls3xx_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::ls3xx::ls3xx_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::ls3xx::ls3xx_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::ls3xx::ls3xx_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::ls3xx::ls3xx_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::ls3xx::ls3xx_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::ls3xx::ls3xx_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::ls3xx::ls3xx_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"de + } + } + #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 ls3xx scriptcontext object (typically sct_ls3xx_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 klass 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 klass 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::ls3xx + 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_3xx() creates a scriptcontext object for a Lakeshore 336 or 340 temperature controller +# @param sct_controller name of the ls3xx scriptcontext object (typically sct_ls3xx_tc1 or tc2) +# @param klass 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_3xx {sct_controller klass tempobj tol LSmodel} { + if [ catch { + set ::scobj::ls3xx::ls3xx_driveTolerance $tol + set ::scobj::ls3xx::ls3xx_LSmodel $LSmodel + set ns ::scobj::ls3xx + set ::scobj::ls3xx::ls3xx_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::ls3xx::ls3xx_term "" ! obsolete + + MakeSICSObj $tempobj SCT_OBJECT + sicslist setatt $tempobj klass $klass + 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::ls3xx::ls3xx_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 $klass $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 $klass $LSmodel + # helpNotes4user $scobj_hpath $cmdGroup $varName + } + + hsetprop $scobj_hpath/input/alarm_Limits_A units $::scobj::ls3xx::ls3xx_tempUnits + hsetprop $scobj_hpath/input/alarm_Limits_B units $::scobj::ls3xx::ls3xx_tempUnits + hsetprop $scobj_hpath/input/alarm_Limits_C units $::scobj::ls3xx::ls3xx_tempUnits + hsetprop $scobj_hpath/input/alarm_Limits_D units $::scobj::ls3xx::ls3xx_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/tolerance plain user float + hsetprop $scobj_hpath/control/tolerance units $::scobj::ls3xx::ls3xx_tempUnits + # hsetprop $scobj_hpath/control/tolerance units "K" + hset $scobj_hpath/control/tolerance $tol + helpNotes4user $scobj_hpath "control" "tolerance" + + #hfactory $scobj_hpath/lowerlimit plain mugger float + #hsetprop $scobj_hpath/lowerlimit units $::scobj::ls3xx::ls3xx_tempUnits + #hset $scobj_hpath/lowerlimit $::scobj::ls3xx::ls3xx_lowerlimit + #hfactory $scobj_hpath/upperlimit plain mugger float + #hsetprop $scobj_hpath/upperlimit units $::scobj::ls3xx::ls3xx_tempUnits + #hset $scobj_hpath/upperlimit $::scobj::ls3xx::ls3xx_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::ls3xx::ls3xx_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 klass priv} $nxProperties { + hsetprop $rootpath/$hpath klass $klass + 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) + ::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 + ls3xx_init $sct_controller $scobj_hpath + puts "Lakeshore $::scobj::ls3xx::ls3xx_LSmodel temperature controller ready at /sample/$tempobj (Driver 2009-11-27)" + } message ] { + return -code error "in mk_sct_lakeshore_3xx $message" + } + } + namespace export mk_sct_lakeshore_3xx + # endproc mk_sct_lakeshore_3xx sct_controller klass tempobj tol ls3xx_LSmodel +} +# end of namespace mk_sct_lakeshore_3xx + +## +# @brief add_ls3xx() 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 3xx) +# @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_ls3xx {name IP port terminator {_tol 1.0} {_ls3xx_LSmodel 340} } { + # 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 { + puts "\nadd_ls3xx: makesctcontroller $name std ${IP}:$port for Lakeshore model $_ls3xx_LSmodel" + makesctcontroller sct_ls3xx_$name std ${IP}:$port $terminator + mk_sct_lakeshore_3xx sct_ls3xx_$name environment $name $_tol $_ls3xx_LSmodel + 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_ls3xx: $message" + } +} + +namespace import ::scobj::ls3xx::* diff --git a/site_ansto/instrument/gumxml.tcl b/site_ansto/instrument/gumxml.tcl index 242c6da0..67b5693b 100644 --- a/site_ansto/instrument/gumxml.tcl +++ b/site_ansto/instrument/gumxml.tcl @@ -33,8 +33,12 @@ proc property_elements {path indent} { foreach {key value} [string map {= " "} [hlistprop $path tcl]] { if {[string compare -nocase $key "control"] == 0} {continue} lappend proplist "$prefix\n" - foreach v [split $value ,] { - lappend proplist "$prefix$prefix$v\n" + if {[string compare -nocase $key "help"] == 0} { + lappend proplist "$prefix$prefix$value\n" + } else { + foreach v [split $value ,] { + lappend proplist "$prefix$prefix$v\n" + } } lappend proplist "$prefix\n" } diff --git a/site_ansto/instrument/hipd/config/commands/commands.tcl b/site_ansto/instrument/hipd/config/commands/commands.tcl index f6a9026f..60696b7b 100644 --- a/site_ansto/instrument/hipd/config/commands/commands.tcl +++ b/site_ansto/instrument/hipd/config/commands/commands.tcl @@ -13,17 +13,21 @@ command SetRadColl { float time float range } { - +catch { set spd [expr 2.0*$range / $time] oct softlowerlim [expr -abs($range/2.0) - 0.5] oct softupperlim [expr abs($range/2.0) + 0.5] oct maxretry 5 oct accel 0.25 oct speed 0.25 + oct decel 0.25 drive oct [expr abs($range/2.0)] oct speed $spd - oct accel $spd + oct accel [expr $spd*5] + oct decel [expr $spd*5] oct maxretry 0 +} msg +clientput $msg } # SimpleRun command SimpleRun { @@ -31,12 +35,12 @@ float=0:inf steptime int=1:inf numsteps } { - RadCollOff +# RadCollOff histmem mode time histmem preset $steptime newfile HISTOGRAM_XY for {set i 0} {$i < $numsteps} {incr i} { - histmem start block + hmm countblock save $i } } @@ -49,13 +53,13 @@ int=1:inf numsteps float=0:inf steptime } { - RadCollOff +# RadCollOff histmem mode time histmem preset $steptime newfile HISTOGRAM_XY for {set i 0} {$i < $numsteps} {incr i} { drive $motor [expr $i*$step+$start] - histmem start block + hmm countblock save $i } } @@ -65,14 +69,16 @@ int=1:inf oscno int=1:inf reps } { - RadCollOn $oscno +# RadCollOn $oscno histmem mode unlimited newfile HISTOGRAM_XY for {set i 0} {$i < $reps} {incr i} { - histmem start block + oscmd start $oscno + hmm countblock save $i } - RadCollOff + oscmd stop +# RadCollOff } # RadCollTimed command RadCollTimed { @@ -105,15 +111,17 @@ int=1:inf numsteps int=1:inf oscno } { - RadCollOn $oscno +# RadCollOn $oscno histmem mode unlimited newfile HISTOGRAM_XY for {set i 0} {$i < $numsteps} {incr i} { drive $motor [expr $i*$step+$start] - histmem start block + oscmd start $oscno + hmm countblock save $i } - RadCollOff +# RadCollOff + oscmd stop } } namespace import ::ajscmds::* diff --git a/site_ansto/instrument/hipd/config/hmm/hmm_configuration.tcl b/site_ansto/instrument/hipd/config/hmm/hmm_configuration.tcl index ec6162c1..05c23b28 100644 --- a/site_ansto/instrument/hipd/config/hmm/hmm_configuration.tcl +++ b/site_ansto/instrument/hipd/config/hmm/hmm_configuration.tcl @@ -30,6 +30,10 @@ proc ::histogram_memory::init_OAT_TABLE {args} { FAT_TABLE -set MULTI_HOST_HISTO_STITCH_OVERLAP 8 MULTI_HOST_HISTO_JOIN_STITCH_ORDER INVERTED OAT_TABLE -set X { 991.5 987.5 } NXC 248 Y { -0.5 3.5 } NYC 128 T { 0 20000 } NTC 1 } + "double_y_968x128" { + FAT_TABLE -set MULTI_HOST_HISTO_STITCH_OVERLAP 8 MULTI_HOST_HISTO_JOIN_STITCH_ORDER INVERTED BNL_STATUS_MASK_MULTI_Y 1000h + OAT_TABLE -set X { 991.5 987.5 } NXC 248 Y { -0.5 3.5 } NYC 256 T { 0 20000 } NTC 1 + } default { # Default to std_968x128 FAT_TABLE -set MULTI_HOST_HISTO_STITCH_OVERLAP 8 MULTI_HOST_HISTO_JOIN_STITCH_ORDER INVERTED diff --git a/site_ansto/instrument/hipd/config/hmm/hmm_rapid.tcl b/site_ansto/instrument/hipd/config/hmm/hmm_rapid.tcl index f41ba500..802e67fb 100644 --- a/site_ansto/instrument/hipd/config/hmm/hmm_rapid.tcl +++ b/site_ansto/instrument/hipd/config/hmm/hmm_rapid.tcl @@ -1,33 +1,89 @@ # Test script for multi period acquisition and saving. +# latest mods 21 Oct 09 based on info from Ferdi - ajs -set nos_periods 1 +set n_periods 1 + +proc open_hist_file {} { + newfile HISTOGRAM_XY +} + +proc histset_XY {} { + set histstr "proc open_hist_file {} {newfile HISTOGRAM_XY}" + eval $histstr +} + +proc histset_XYperiod {} { + set histstr "proc open_hist_file {} {newfile HISTOPERIOD_XY}" + eval $histstr +} + +proc histset_XYT {} { + set histstr "proc open_hist_file {} {newfile HISTOGRAM_XYT}" + eval $histstr +} + +proc histset_XYTperiod {} { + set histstr "proc open_hist_file {} {newfile HISTOPERIOD_XYT}" +} proc histmem_period_single { } { # Because bugs may not yet have been corrected in hmm_configuration_common_1.tcl, # apply the default settings before attempting del/clear. - global nos_periods - set nos_periods 1 + global n_periods + set n_periods 1 FAT_TABLE -set NOS_PERIODS 1 - FAT_TABLE -del NOS_PERIODS - BAT_TABLE -set PERIOD_INDICES { 0 } NO_BAT_ENTRIES 1 NO_BAT_PERIODS 1 NO_REPEAT_ENTRY 0 NO_REPEAT_TABLE 0 NO_EXECUTE_TABLE 0 - BAT_TABLE -clear +# FAT_TABLE -del NOS_PERIODS +# BAT_TABLE -set PERIOD_INDICES { 0 } NO_BAT_ENTRIES 1 NO_BAT_PERIODS 1 NO_REPEAT_ENTRY 0 NO_REPEAT_TABLE 0 NO_EXECUTE_TABLE 0 + BAT_TABLE -set PERIOD_INDICES { 0 } NO_BAT_ENTRIES 1 NO_BAT_PERIODS 1 NO_REPEAT_ENTRY 1 NO_REPEAT_TABLE 1 NO_EXECUTE_TABLE 1 + histmem loadconf +# BAT_TABLE -clear + histset_XY } Publish histmem_period_single User -proc histmem_period_sequence { Np Nf } { +# orig version of this pre Oct 09 +# +#proc histmem_period_sequence { Np Nf } { # Note - sequence needs to be at least 2 periods. - global nos_periods - set nos_periods $Np - FAT_TABLE -set NOS_PERIODS $Np - BAT_TABLE -set PERIOD_INDICES { 0 1 } NO_BAT_ENTRIES $Np NO_BAT_PERIODS $Np NO_REPEAT_ENTRY $Nf NO_REPEAT_TABLE 1 NO_EXECUTE_TABLE 1 +# global n_periods +# set n_periods $Np +# set fatstr "FAT_TABLE -set NOS_PERIODS $Np" +# set fatstr2 "FAT_TABLE -set PERIOD_INDICES { 0 1 }" +# set batstr "BAT_TABLE -set NO_BAT_ENTRIES $Np NO_BAT_PERIODS $Np NO_REPEAT_ENTRY $Nf NO_REPEAT_TABLE 1 NO_EXECUTE_TABLE 1" +# eval $fatstr +# eval $fatstr2 +# eval $batstr +# histmem loadconf +# +#} + +proc histmem_period_sequence {Np} { + global n_periods + set n_periods $Np + FAT_TABLE -set NOS_PERIODS $Np + BAT_TABLE -set PERIOD_INDICES { 0 } NO_BAT_ENTRIES 1 NO_BAT_PERIODS 1 NO_REPEAT_ENTRY 1 NO_REPEAT_TABLE 1 NO_EXECUTE_TABLE $Np + histmem loadconf + histset_XYperiod +} + +proc histmem_period_sequence_off {} { + global n_periods + set n_periods $Np + FAT_TABLE -set NOS_PERIODS 1 + BAT_TABLE -set PERIOD_INDICES { 0 } NO_BAT_ENTRIES 1 NO_BAT_PERIODS 1 NO_REPEAT_ENTRY 0 NO_REPEAT_TABLE 0 NO_EXECUTE_TABLE 0 + histmem loadconf + histset_XYperiod } Publish histmem_period_sequence User + + + proc histmem_save_period_sequence { } { - global nos_periods - newfile HISTOGRAM_XYT - for { set i 0 } { $i < $nos_periods } { incr i } { - hmm configure read_data_period_number $i + global n_periods + open_hist_file + for { set i 0 } { $i < $n_periods } { incr i } { +# hmm configure read_data_period_number $i save $i } } @@ -42,6 +98,7 @@ proc histmem_period_strobo {Freq Bins} { # OAT_TABLE -set T { 0 $bintime } NTC $Bins eval $oatstr histmem loadconf + histset_XYT } Publish histmem_period_strobo User @@ -49,6 +106,7 @@ proc histmem_strobo_off {} { histmem fsrce INTERNAL OAT_TABLE -set T { 0 200000 } NTC 1 histmem loadconf + histset_XY } Publish histmem_strobo_off User @@ -69,8 +127,8 @@ proc histmem_acquire_period_sequence { Np Nf } { # clientput "tc1 reading is: " [tc1 get] - global nos_periods - for { set i 0 } { $i < $nos_periods } { incr i } { + global n_periods + for { set i 0 } { $i < $n_periods } { incr i } { # after $dlytime # set current_period -1 # while { $current_period < $i } { diff --git a/site_ansto/instrument/hipd/config/motors/euler_configuration.tcl b/site_ansto/instrument/hipd/config/motors/euler_configuration.tcl index 0fba5a34..52711410 100644 --- a/site_ansto/instrument/hipd/config/motors/euler_configuration.tcl +++ b/site_ansto/instrument/hipd/config/motors/euler_configuration.tcl @@ -2,7 +2,7 @@ set ephi_Home 6647698 set echi_Home 8919294 -set eom_Home 22960659 +set eom_Home 23165482 # Sample Tilt 1, euler omega stage Motor eom $motor_driver_type [params \ @@ -30,8 +30,8 @@ Motor echi $motor_driver_type [params \ asyncqueue mc2\ axis B\ units degrees\ - hardlowerlim -20\ - hardupperlim 95\ + hardlowerlim -32\ + hardupperlim 97\ maxSpeed 1\ maxAccel 1\ maxDecel 1\ diff --git a/site_ansto/instrument/hipd/config/motors/motor_configuration.tcl b/site_ansto/instrument/hipd/config/motors/motor_configuration.tcl index 1f7a48df..2dbabeb2 100644 --- a/site_ansto/instrument/hipd/config/motors/motor_configuration.tcl +++ b/site_ansto/instrument/hipd/config/motors/motor_configuration.tcl @@ -32,11 +32,14 @@ if {$sim_mode == "true"} { } #Measured absolute encoder reading at home position -set mx_Home 7464891 +#set mx_Home 7464891 +set mx_Home 23263535 set mom_Home 9274794 set mtth_Home 19927837 -set mphi_Home 7613516 -set mchi_Home 9050090 +#set mphi_Home 7613516 +set mphi_Home 27847793 +#set mchi_Home 9050090 +set mchi_Home 25561619 set my_Home 6767221 set som_Home 17214054 set stth_Home 2896180 @@ -241,7 +244,7 @@ Motor mtth $motor_driver_type [params \ asyncqueue mc1\ axis F\ units degrees\ - hardlowerlim 45\ + hardlowerlim 40.7\ hardupperlim 120.5\ maxSpeed 0.2\ maxAccel 0.2\ @@ -253,7 +256,7 @@ Motor mtth $motor_driver_type [params \ absEnc 1\ absEncHome $mtth_Home\ cntsPerX -93207] -mtth softlowerlim 45 +mtth softlowerlim 40.75 mtth softupperlim 120 mtth home 90 mtth speed 0.2 @@ -281,7 +284,7 @@ Motor som $motor_driver_type [params \ units degrees\ hardlowerlim -110\ hardupperlim 160\ - maxSpeed 1\ + maxSpeed 5\ maxAccel 1\ maxDecel 1\ stepsPerX 12500\ @@ -299,7 +302,7 @@ Motor stth $motor_driver_type [params \ asyncqueue mc2\ axis F\ units degrees\ - hardlowerlim -120\ + hardlowerlim 5\ hardupperlim 30\ maxSpeed 0.5\ maxAccel 0.1\ @@ -308,7 +311,7 @@ Motor stth $motor_driver_type [params \ absEnc 1\ absEncHome $stth_Home\ cntsPerX -93207] -stth softlowerlim -120 +stth softlowerlim 5 stth softupperlim 30 stth home 0 stth speed 0.5 @@ -323,16 +326,16 @@ Motor oct $motor_driver_type [params \ axis G\ units degrees\ hardlowerlim -2\ - hardupperlim 2\ - maxSpeed 1.0\ - maxAccel 1.0\ - maxDecel 1.0\ + hardupperlim 4.2\ + maxSpeed 2.0\ + maxAccel 5.0\ + maxDecel 5.0\ stepsPerX 56762\ absEnc 1\ absEncHome $oct_Home\ cntsPerX -465] oct softlowerlim -2 -oct softupperlim 2 +oct softupperlim 4 oct home 0 oct speed 0.5 oct precision 0.01 @@ -373,17 +376,17 @@ Motor mf2 $motor_driver_type [params \ axis A\ units degrees\ hardlowerlim 0\ - hardupperlim 1\ - maxSpeed 1\ - maxAccel 1\ - maxDecel 1\ + hardupperlim 2\ + maxSpeed 0.1\ + maxAccel 0.1\ + maxDecel 0.1\ stepsPerX 22000\ absEnc 1\ absEncHome 350\ cntsPerX 14000] -setHomeandRange -motor mf2 -home 0 -lowrange 0 -uprange 1 -mf2 speed 0.1 -mf2 precision 0.01 +setHomeandRange -motor mf2 -home 0 -lowrange 0 -uprange 2 +mf2 speed 0.02 +mf2 precision 0.005 mf2 part monochromator mf2 long_name mf2 diff --git a/site_ansto/instrument/hipd/wombat_configuration.tcl b/site_ansto/instrument/hipd/wombat_configuration.tcl index 96e29948..9f28d006 100644 --- a/site_ansto/instrument/hipd/wombat_configuration.tcl +++ b/site_ansto/instrument/hipd/wombat_configuration.tcl @@ -1,5 +1,5 @@ -# $Revision: 1.31 $ -# $Date: 2009-11-24 22:56:46 $ +# $Revision: 1.32 $ +# $Date: 2009-12-09 06:07:45 $ # Author: Ferdi Franceschini (ffr@ansto.gov.au) # Last revision by: $Author: ffr $ @@ -18,7 +18,6 @@ source server_config.tcl # INSTRUMENT SPECIFIC CONFIGURATION fileeval $cfPath(motors)/motor_configuration.tcl - source instrument_vars.tcl fileeval $cfPath(source)/source.tcl @@ -38,9 +37,21 @@ fileeval $cfPath(anticollider)/anticollider.tcl fileeval $cfPath(hmm)/hmm_rapid.tcl source gumxml.tcl -::environment::temperature::add_ls340 tc1 1 -::environment::temperature::add_ls340 tc2 2 -#::environment::temperature::add_west400 + +# Drivers for Lakeshore Temperature Controller +# old Lakeshore driver (c-code) + ::environment::temperature::add_ls340 tc1 1 +# ::environment::temperature::add_ls340 tc2 2 + +# New Lakeshore series driver for models 340 and 336 - (tcl-code, version 20090824) - beta status +# driverName shortName IP-address portOnMoxa temperatureTolerance LakeshoreModel +# ffr 2009-11-11: NOTE I've added a line terminator argument to the ls3xx setup. +# You need to set the line termintator to either "\r\n" for CRLF or "\r" for CR +# add_ls3xx tc1 137.157.201.85 7777 "\r\n" 0.5 336 +# add_ls3xx tc2 137.157.201.85 4002 "\r\n" 0.5 340 + + +#::environment::temperature::add_west400 server_init ########################################### # WARNING: Do not add any code below server_init, if you do SICS may fail to initialise properly. diff --git a/site_ansto/instrument/reflectometer/config/INSTCFCOMMON.TXT b/site_ansto/instrument/reflectometer/config/INSTCFCOMMON.TXT index 55c528b6..eefe9275 100644 --- a/site_ansto/instrument/reflectometer/config/INSTCFCOMMON.TXT +++ b/site_ansto/instrument/reflectometer/config/INSTCFCOMMON.TXT @@ -3,6 +3,7 @@ config/anticollider/anticollider_common.tcl config/plc/plc_common_1.tcl config/counter/counter_common_1.tcl config/environment/temperature/sct_lakeshore_3xx.tcl +config/environment/magneticField/sct_bruker_BEC1.tcl config/hipadaba/hipadaba_configuration_common.tcl config/hipadaba/common_instrument_dictionary.tcl config/hipadaba/instdict_specification.tcl diff --git a/site_ansto/instrument/sans/config/commands/commands.tcl b/site_ansto/instrument/sans/config/commands/commands.tcl index 8a6b657c..8bf1532b 100644 --- a/site_ansto/instrument/sans/config/commands/commands.tcl +++ b/site_ansto/instrument/sans/config/commands/commands.tcl @@ -16,55 +16,72 @@ namespace eval motor { # @param bs beamstop, 1,2,3,4,5 or 6 # @param bx beam position in detector coordinates # @param bz beam position in detector coordinates -# Origin of beamstop coordinate system is (bsxo, bszo) relative to frame -# Origin of detector coordinate system is (0, 0) relative to frame # Given -# (Xb,Zb) = beam position in frame coordinates -# bx = beam x pos in detector coordinates -# bz = beam z pos in detector coordinates -# detoff = detector x pos in detector coordinates -# bsx = beamstop x pos in beamstop coordinates -# bsz = beamstop z pos in beamstop coordinates -# bszo = beamstop z origin relative to frame -# (bx+detoff, bz) = (Xb,Zb) -# (bsx, bsz+bszo) = (Xb,Zb) +# (Xbf,Zbf) = beam pos in frame coords +# (Xbd,Zbd) = beam pos in detector coords +# (Xbbs, Zbbs) = beam pos in beamstop coords +# (Xdf,Zdf) = detector pos in frame coords +# (Xbsf,Zbsf) = beamstop pos in frame coords +# +# (Xbf,Zbf) = (Xdf+Xbd, Zdf+Zbd) = (Xbsf+Xbbs, Zbsf+Zbbs) +# Origin of detector coords = frame origin +# +# Detector and beamstop motor readings with beamstop disk centers +# overlapping over detector center mark. +# Xdf = 264.542 (7283813) Zdf = 0 +# Xbbs = 296.291 (8054270) +# Zbbs = 259.641 (13488244) +# Xbd = dethw, Zbd = (dethh) +# +# 264.542+dethw = Xbsf+296.291 +# 0+dethh = Zbsf+259.641 +# +# Xbsf = -31.749 + dethw, Zbsf = -259.641 + dethh proc selbs {bs {bx "UNDEF"} {bz "UNDEF"}} { set bsdriving false - set dethw [expr {[SplitReply detector_active_height_mm]/2.0}] - set dethh [expr {[SplitReply detector_active_width_mm]/2.0}] - set bsxo -33.112 + $dethw - set bszo -329.440 + $dethh + set dethw [expr {[SplitReply [detector_active_height_mm]]/2.0}] + set dethh [expr {[SplitReply [detector_active_width_mm]]/2.0}] + set Xbsf [expr -31.749 + $dethw] + set Zbsf [expr -259.641 + $dethh] + array set bsl [subst { + 1 [SplitReply [bs1 softlowerlim]] + 2 [SplitReply [bs2 softlowerlim]] + 3 [SplitReply [bs3 softlowerlim]] + 4 [SplitReply [bs4 softlowerlim]] + 5 [SplitReply [bs5 softlowerlim]] + 6 [SplitReply [bs6 softlowerlim]] + }] if [ catch { switch $bs { "1" { set bsmot "bs1" set bs_target 92.79 - set bsdownCmd "drive bs2 0 bs3 0 bs4 0 bs5 0 bs6 0" + set bsdownCmd "drive bs2 $bsl(2) bs3 $bsl(3) bs4 $bsl(4) bs5 $bsl(5) bs6 $bsl(6)" } "2" { set bsmot "bs2" set bs_target 86.20 - set bsdownCmd "drive bs1 0 bs3 0 bs4 0 bs5 0 bs6 0" + set bsdownCmd "drive bs1 $bsl(1) bs3 $bsl(3) bs4 $bsl(4) bs5 $bsl(5) bs6 $bsl(6)" } "3" { set bsmot "bs3" set bs_target 92.54 - set bsdownCmd "drive bs2 0 bs1 0 bs4 0 bs5 0 bs6 0" + set bsdownCmd "drive bs2 $bsl(2) bs1 $bsl(1) bs4 $bsl(4) bs5 $bsl(5) bs6 $bsl(6)" } "4" { set bsmot "bs4" set bs_target 86.66 - set bsdownCmd "drive bs2 0 bs3 0 bs1 0 bs5 0 bs6 0" + set bsdownCmd "drive bs2 $bsl(2) bs3 $bsl(3) bs1 $bsl(1) bs5 $bsl(5) bs6 $bsl(6)" } "5" { set bsmot "bs5" set bs_target 92.50 - set bsdownCmd "drive bs2 0 bs3 0 bs4 0 bs1 0 bs6 0" + set bsdownCmd "drive bs2 $bsl(2) bs3 $bsl(3) bs4 $bsl(4) bs1 $bsl(1) bs6 $bsl(6)" } "6" { set bsmot "bs6" set bs_target 85.87 - set bsdownCmd "drive bs2 0 bs3 0 bs4 0 bs5 0 bs1 0" + set bsdownCmd "drive bs2 $bsl(2) bs3 $bsl(3) bs4 $bsl(4) bs5 $bsl(5) bs1 $bsl(1)" } default { error "beamstop selection must be an integer from 1 to 6" @@ -76,8 +93,8 @@ proc selbs {bs {bx "UNDEF"} {bz "UNDEF"}} { if {$bx == "UNDEF" || $bz == "UNDEF"} { statemon start selbs set bsdriving true - drive $bsmot $bs_target BeamStop -1 + drive $bsmot $bs_target eval $bsdownCmd BeamStop $bs set bsdriving false @@ -88,13 +105,13 @@ proc selbs {bs {bx "UNDEF"} {bz "UNDEF"}} { error "beam coordinates must be floats" } } - set bsx_target [expr {$bx-$bsxo+$detoff_val}] - set bsz_target [expr {$bz-$bszo}] + set bsx_target [expr {$bx-$Xbsf+$detoff_val}] + set bsz_target [expr {$bz-$Zbsf}] statemon start selbs set bsdriving true - drive $bsmot $bs_target bsx $bsx_target bsz $bsz_target BeamStop -1 + drive $bsmot $bs_target bsx $bsx_target bsz $bsz_target eval $bsdownCmd BeamStop $bs set bsdriving false diff --git a/site_ansto/instrument/sans/config/motors/motor_configuration.tcl b/site_ansto/instrument/sans/config/motors/motor_configuration.tcl index e351f867..422d31ba 100644 --- a/site_ansto/instrument/sans/config/motors/motor_configuration.tcl +++ b/site_ansto/instrument/sans/config/motors/motor_configuration.tcl @@ -1,5 +1,5 @@ -# $Revision: 1.29 $ -# $Date: 2009-12-03 02:04:57 $ +# $Revision: 1.30 $ +# $Date: 2009-12-09 06:07:45 $ # Author: Ferdi Franceschini (ffr@ansto.gov.au) # Last revision by: $Author: ffr $ @@ -76,12 +76,20 @@ set att_Home 24782942 set bsx_Home 7578346 #set bsz_Home 10143000 set bsz_Home 10568857 -set bs1_Home 13219 -set bs2_Home 2506 -set bs3_Home 22391 -set bs4_Home 15287 -set bs5_Home 6283 -set bs6_Home 19123 +#set bs1_Home 13219 +#set bs2_Home 2506 +#set bs3_Home 22391 +#set bs4_Home 15287 +#set bs5_Home 6283 +#set bs6_Home 19123 + +set bs1_Home 21298 +set bs2_Home 9942 +set bs3_Home 30928 +set bs4_Home 22111 +set bs5_Home 14558 +set bs6_Home 27251 + set bs_cntsPerX [expr 32768.0/360.0] set bs_stepsPerX [expr -25000.0*160.0/360.0] set pol_Home 7500000 @@ -406,8 +414,8 @@ Motor detoff $motor_driver_type [params \ port pmc1-quokka\ axis H\ units mm\ - hardlowerlim -50\ - hardupperlim 500\ + hardlowerlim -9\ + hardupperlim 465\ maxSpeed 10\ maxAccel 1\ maxDecel 10\ @@ -417,14 +425,14 @@ Motor detoff $motor_driver_type [params \ cntsPerX [expr 8192.0/5.08]] detoff part detector detoff long_name detector_x -detoff softlowerlim -50 -detoff softupperlim 500 +detoff softlowerlim 0 +detoff softupperlim 450 detoff home 0 ############################ # Motor Controller 2 # Motor Controller 2 -# Motor Controller 2 +# Motor Controller 25 ############################ #:TP at HOME # @@ -834,7 +842,7 @@ Motor bsx $motor_driver_type [params \ axis A\ units mm\ hardlowerlim 0\ - hardupperlim 500\ + hardupperlim 317\ maxSpeed 5\ maxAccel 2\ maxDecel 5\ @@ -845,7 +853,7 @@ Motor bsx $motor_driver_type [params \ bsx part detector bsx long_name bsx bsx softlowerlim 0 -bsx softupperlim 500 +bsx softupperlim 310 bsx speed 5 bsx home 0 @@ -857,7 +865,7 @@ Motor bsz $motor_driver_type [params \ axis B\ units mm\ hardlowerlim 0\ - hardupperlim 400\ + hardupperlim 280\ maxSpeed 5\ maxAccel 1\ maxDecel 5\ @@ -868,7 +876,7 @@ Motor bsz $motor_driver_type [params \ bsz part detector bsz long_name bsz bsz softlowerlim 0 -bsz softupperlim 400 +bsz softupperlim 275 bsz home 0 # Largest beamstop @@ -876,8 +884,8 @@ Motor bs1 $motor_driver_type [params \ asyncqueue mc4\ axis C\ units degrees\ - hardlowerlim 0\ - hardupperlim 100\ + hardlowerlim 9\ + hardupperlim 95\ maxSpeed 2\ maxAccel 1\ maxDecel 1\ @@ -887,9 +895,9 @@ Motor bs1 $motor_driver_type [params \ cntsPerX $bs_cntsPerX] bs1 part detector bs1 long_name bs1 -bs1 softlowerlim 0 -bs1 softupperlim 100 -bs1 home 0 +bs1 softlowerlim 11 +bs1 softupperlim 95 +bs1 home 90 bs1 precision 0.05 sicslist setatt bs1 link parameters_group @@ -897,8 +905,8 @@ Motor bs2 $motor_driver_type [params \ asyncqueue mc4\ axis D\ units degrees\ - hardlowerlim 0\ - hardupperlim 100\ + hardlowerlim 6\ + hardupperlim 95\ maxSpeed 2\ maxAccel 1\ maxDecel 1\ @@ -908,9 +916,9 @@ Motor bs2 $motor_driver_type [params \ cntsPerX $bs_cntsPerX] bs2 part detector bs2 long_name bs2 -bs2 softlowerlim 0 -bs2 softupperlim 100 -bs2 home 0 +bs2 softlowerlim 7 +bs2 softupperlim 95 +bs2 home 90 bs2 precision 0.05 sicslist setatt bs2 link parameters_group @@ -918,8 +926,8 @@ Motor bs3 $motor_driver_type [params \ asyncqueue mc4\ axis E\ units degrees\ - hardlowerlim 0\ - hardupperlim 100\ + hardlowerlim 9\ + hardupperlim 95\ maxSpeed 2\ maxAccel 1\ maxDecel 1\ @@ -929,9 +937,9 @@ Motor bs3 $motor_driver_type [params \ cntsPerX $bs_cntsPerX] bs3 part detector bs3 long_name bs3 -bs3 softlowerlim 0 -bs3 softupperlim 100 -bs3 home 0 +bs3 softlowerlim 11 +bs3 softupperlim 95 +bs3 home 90 bs3 precision 0.05 sicslist setatt bs3 link parameters_group @@ -939,8 +947,8 @@ Motor bs4 $motor_driver_type [params \ asyncqueue mc4\ axis F\ units degrees\ - hardlowerlim 0\ - hardupperlim 100\ + hardlowerlim 6\ + hardupperlim 95\ maxSpeed 2\ maxAccel 1\ maxDecel 1\ @@ -950,9 +958,9 @@ Motor bs4 $motor_driver_type [params \ cntsPerX $bs_cntsPerX] bs4 part detector bs4 long_name bs4 -bs4 softlowerlim 0 -bs4 softupperlim 100 -bs4 home 0 +bs4 softlowerlim 7 +bs4 softupperlim 95 +bs4 home 90 bs4 precision 0.05 sicslist setatt bs4 link parameters_group @@ -960,8 +968,8 @@ Motor bs5 $motor_driver_type [params \ asyncqueue mc4\ axis G\ units degrees\ - hardlowerlim 0\ - hardupperlim 100\ + hardlowerlim 9\ + hardupperlim 95\ maxSpeed 2\ maxAccel 1\ maxDecel 1\ @@ -971,9 +979,9 @@ Motor bs5 $motor_driver_type [params \ cntsPerX $bs_cntsPerX] bs5 part detector bs5 long_name bs5 -bs5 softlowerlim 0 -bs5 softupperlim 100 -bs5 home 0 +bs5 softlowerlim 11 +bs5 softupperlim 95 +bs5 home 90 bs5 precision 0.05 sicslist setatt bs5 link parameters_group @@ -981,8 +989,8 @@ Motor bs6 $motor_driver_type [params \ asyncqueue mc4\ axis H\ units degrees\ - hardlowerlim 0\ - hardupperlim 100\ + hardlowerlim 6\ + hardupperlim 95\ maxSpeed 2\ maxAccel 1\ maxDecel 1\ @@ -992,9 +1000,9 @@ Motor bs6 $motor_driver_type [params \ cntsPerX $bs_cntsPerX] bs6 part detector bs6 long_name bs6 -bs6 softlowerlim 0 -bs6 softupperlim 100 -bs6 home 0 +bs6 softlowerlim 7 +bs6 softupperlim 95 +bs6 home 90 bs6 precision 0.05 sicslist setatt bs6 link parameters_group diff --git a/site_ansto/instrument/sans/config/velsel/velsel.tcl b/site_ansto/instrument/sans/config/velsel/velsel.tcl index 46bd3ef1..1fcf27b1 100644 --- a/site_ansto/instrument/sans/config/velsel/velsel.tcl +++ b/site_ansto/instrument/sans/config/velsel/velsel.tcl @@ -1,4 +1,4 @@ -set currVelSel "two" +set currVelSel 43 namespace eval ::scobj::velocity_selector { variable blocked_speeds @@ -7,7 +7,7 @@ namespace eval ::scobj::velocity_selector { set ::currVelSel [string tolower $::currVelSel] switch $::currVelSel { - "one" { + 40 { set velsel_IP "137.157.202.73" set velsel_port 10000 set m_dTwistAngle 48.30 @@ -24,7 +24,7 @@ namespace eval ::scobj::velocity_selector { 28301 inf } } - "two" { + 43 { # dc2-quokka.nbi.ansto.gov.au set velsel_IP "137.157.202.74" set velsel_port 10000 diff --git a/site_ansto/instrument/util/utility.tcl b/site_ansto/instrument/util/utility.tcl index bef632a9..5a619c8c 100644 --- a/site_ansto/instrument/util/utility.tcl +++ b/site_ansto/instrument/util/utility.tcl @@ -1,7 +1,7 @@ # Some useful functions for SICS configuration. -# $Revision: 1.22 $ -# $Date: 2009-11-24 22:56:49 $ +# $Revision: 1.23 $ +# $Date: 2009-12-09 06:07:45 $ # Author: Ferdi Franceschini (ffr@ansto.gov.au) # Last revision by $Author: ffr $ @@ -871,6 +871,47 @@ proc hsibPath {sibling} { set hpath [sct] return [file dirname $hpath]/$sibling } + +## +# @brief Handle exceptions caught by a 'catch' command. +# Note: You must use 'error' not 'return -code error' to +# raise errors from within a catch block. +# +# @param status, the status returned by the 'catch' command. +# @param message, the message set by the 'catch' command. +# +# Call this as the last command in the command block or +# for loop which encloses the 'catch' +proc handle_exception {status message} { + switch $status { + 0 { + # TCL_OK, This is raised when you just drop out of the + # bottom of a 'catch' command. + return -code ok + } + 1 { + # TCL_ERROR + return -code error "([info level -1]) $message" + } + 2 { + # TCL_RETURN + return -code return "$message" + } + 3 { + # TCL_BREAK + return -code break + } + 4 { + # TCL_CONTINUE + return -code continue + } + default { + # Propogate user defined return codes with message + return -code $status "$message" + } + } +} + namespace import ::utility::*; Publish getinfo spy Publish setpos user