Add bath temperature to feedback. Allow switching between reading the bath or external sensor for the sensor value. Check the data type of replies from the Julabo because it can return values like "___.__" if you're reading the wrong sensor. sct_lakeshore_340.tcl, sct_lakeshore_336.tcl Use the tolerance set on the control/tolerance node so the user can change it while running. r3348 | ffr | 2012-01-20 15:25:46 +1100 (Fri, 20 Jan 2012) | 10 lines
435 lines
15 KiB
Tcl
435 lines
15 KiB
Tcl
# Define procs in ::scobj::xxx namespace
|
|
# MakeSICSObj $obj SCT_<class>
|
|
# The MakeSICSObj cmd adds a /sics/$obj node. NOTE the /sics node is not browsable.
|
|
|
|
|
|
namespace eval ::scobj::lh45 {
|
|
# Temperature controllers must have at least the following nodes
|
|
# /tempcont/setpoint
|
|
# /tempcont/sensor/value
|
|
proc getValue {nextState cmd} {
|
|
if [hpropexists [sct] geterror] {
|
|
hdelprop [sct] geterror
|
|
}
|
|
sct send $cmd
|
|
return $nextState
|
|
}
|
|
proc getTemp {nextState} {
|
|
if [hpropexists [sct] geterror] {
|
|
hdelprop [sct] geterror
|
|
}
|
|
sct send [sct tempsensor]
|
|
return $nextState
|
|
}
|
|
proc setValue {nextState cmd} {
|
|
set par [sct target]
|
|
sct send "$cmd $par"
|
|
return $nextState
|
|
}
|
|
|
|
proc setPoint {tc_root nextState cmd} {
|
|
if {[hval $tc_root/remote_ctrl] == "False"} {
|
|
sct driving 0
|
|
return idle
|
|
}
|
|
set par [sct target]
|
|
|
|
# if {[hval $tc_root/apply_tolerance]} {
|
|
# set tol [hval $tc_root/tolerance]
|
|
# hset $tc_root/subtemp_warnlimit [expr {$par - $tol}]
|
|
# hset $tc_root/overtemp_warnlimit [expr {$par + $tol}]
|
|
# }
|
|
hset $tc_root/power 1
|
|
hset $tc_root/status "busy"
|
|
if {[sct writestatus] == "start"} {
|
|
# Called by drive adapter
|
|
hsetprop $tc_root/setpoint driving 1
|
|
}
|
|
|
|
sct send "$cmd $par"
|
|
sct setpoint_pending 0
|
|
return $nextState
|
|
}
|
|
|
|
proc rdValue {{type "wordchar"}} {
|
|
set data [sct result]
|
|
switch -glob -- $data {
|
|
"ASCERR:*" {
|
|
sct geterror $data
|
|
}
|
|
default {
|
|
if [string is $type $data] {
|
|
if {$data != [sct oldval]} {
|
|
sct oldval $data
|
|
sct update $data
|
|
sct utime readtime
|
|
}
|
|
} else {
|
|
set errmsg "[sct] Expected $data to be of type $type"
|
|
sct geterror $errmsg
|
|
error $errmsg
|
|
}
|
|
}
|
|
}
|
|
return idle
|
|
}
|
|
|
|
##
|
|
# @brief Check if temperature controller is on or off
|
|
#
|
|
# Julabo LH45 Manual pg41
|
|
# If a time of 00:00 is set for a profile, the profile is continued with
|
|
# the next section only after the setpoint temperature (±0.2 °C) is
|
|
# reached.
|
|
# That's good enough for me.
|
|
#
|
|
# When the power is off no temp is outside of any range, therefore we
|
|
# report that the T.C. is in tolerance. This implies that counters
|
|
# can continue running if the T.C. is off, also lets you acquire data
|
|
# while allowing a sample to cool to ambient temperature.
|
|
proc rdPower {tc_root} {
|
|
set power [sct result]
|
|
sct utime readtime
|
|
switch -glob -- $power {
|
|
"ASCERR:*" {
|
|
sct geterror $power
|
|
}
|
|
default {
|
|
set oldval [sct oldval]
|
|
if {$oldval == "UNKNOWN"} { sct utime timecheck }
|
|
if {$power != $oldval} {
|
|
sct oldval $power
|
|
sct update $power
|
|
}
|
|
if {$power==1} {
|
|
if [hgetpropval $tc_root/setpoint setpoint_pending] {
|
|
# Don't update status while we're waiting for the new setpoint to be sent
|
|
return idle
|
|
}
|
|
set currtime [sct readtime]
|
|
set timecheck [sct timecheck]
|
|
set temp [hval $tc_root/sensor/value]
|
|
set setpoint [hval $tc_root/setpoint]
|
|
set tol [hval $tc_root/tolerance]
|
|
if { abs($temp - $setpoint) > $tol } {
|
|
hset $tc_root/emon/isintol 0
|
|
set intol 0
|
|
sct utime timecheck
|
|
} else {
|
|
set timeout [hval $tc_root/tolerance/settletime]
|
|
if { [expr $currtime - $timecheck] > $timeout } {
|
|
hset $tc_root/emon/isintol 1
|
|
set intol 1
|
|
} else {
|
|
set intol 0
|
|
}
|
|
}
|
|
set setpt [hval $tc_root/setpoint]
|
|
set temp [hval $tc_root/sensor/value]
|
|
if { $intol } {
|
|
hsetprop $tc_root/setpoint driving 0
|
|
hset $tc_root/emon/monmode "monitor"
|
|
hset $tc_root/status "idle"
|
|
} else {
|
|
hset $tc_root/emon/monmode "drive"
|
|
hset $tc_root/status "busy"
|
|
}
|
|
} else {
|
|
hset $tc_root/emon/monmode "idle"
|
|
hset $tc_root/emon/isintol 1
|
|
hset $tc_root/status "idle"
|
|
}
|
|
}
|
|
}
|
|
return idle
|
|
}
|
|
|
|
proc getState {tc_root nextState cmd} {
|
|
sct send $cmd
|
|
return $nextState
|
|
}
|
|
|
|
proc checktol {tc_root currtime timecheck} {
|
|
set temp [hval $tc_root/sensor/value]
|
|
set setpoint [hval $tc_root/setpoint]
|
|
set tol [hval $tc_root/tolerance]
|
|
if { abs($temp - $setpoint) > $tol } {
|
|
hset $tc_root/emon/isintol 0
|
|
return 0
|
|
} else {
|
|
set timeout [hval $tc_root/tolerance/settletime]
|
|
if { [expr $currtime - $timecheck] > $timeout } {
|
|
hset $tc_root/emon/isintol 1
|
|
return 1
|
|
} else {
|
|
return 0
|
|
}
|
|
}
|
|
}
|
|
|
|
##
|
|
# @brief Reads the current lh45 state and error messages.
|
|
proc rdState {tc_root} {
|
|
set data [sct result]
|
|
if {[string first "ASCERR:" $data] >=0} {
|
|
sct geterror $data
|
|
} elseif {$data != [sct oldval]} {
|
|
sct oldval $data
|
|
sct update $data
|
|
sct utime readtime
|
|
switch -- [lindex $data 0] {
|
|
"00" {
|
|
hset $tc_root/remote_ctrl False
|
|
}
|
|
"01" {
|
|
hset $tc_root/remote_ctrl False
|
|
}
|
|
"02" {
|
|
hset $tc_root/remote_ctrl True
|
|
}
|
|
"03" {
|
|
hset $tc_root/remote_ctrl True
|
|
}
|
|
default {
|
|
hset $tc_root/remote_ctrl UNKNOWN
|
|
hset $tc_root/lh45_lasterror $data
|
|
error $data
|
|
}
|
|
}
|
|
}
|
|
return idle
|
|
}
|
|
|
|
proc noResponse {} {
|
|
return idle
|
|
}
|
|
proc wrtValue {wcmd args} {
|
|
}
|
|
|
|
proc check {tc_root} {
|
|
set setpoint [sct target]
|
|
set lolimit [hval $tc_root/lowerlimit]
|
|
set hilimit [hval $tc_root/upperlimit]
|
|
if {$setpoint < $lolimit || $setpoint > $hilimit} {
|
|
sct driving 0
|
|
error "setpoint violates limits"
|
|
}
|
|
sct setpoint_pending 1
|
|
return OK
|
|
}
|
|
|
|
##
|
|
# @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 {[sct driving]} {
|
|
return busy
|
|
} else {
|
|
return idle
|
|
}
|
|
}
|
|
|
|
proc halt {tc_root} {
|
|
hset $tc_root/setpoint [hval $tc_root/sensor/value]
|
|
hsetprop $tc_root/setpoint driving 0
|
|
return idle
|
|
}
|
|
|
|
proc mk_sct_julabo_lh45 {sct_controller klass tempobj tol} {
|
|
if {[ catch {
|
|
set ns ::scobj::lh45
|
|
|
|
MakeSICSObj $tempobj SCT_OBJECT
|
|
sicslist setatt $tempobj klass $klass
|
|
sicslist setatt $tempobj long_name $tempobj
|
|
|
|
set scobj_hpath /sics/$tempobj
|
|
hfactory $scobj_hpath/setpoint plain user float
|
|
hsetprop $scobj_hpath/setpoint read ${ns}::getValue rdValue "in_sp_00"
|
|
hsetprop $scobj_hpath/setpoint write ${ns}::setPoint $scobj_hpath noResponse "out_sp_00"
|
|
hsetprop $scobj_hpath/setpoint check ${ns}::check $scobj_hpath
|
|
hsetprop $scobj_hpath/setpoint rdValue ${ns}::rdValue "double"
|
|
hsetprop $scobj_hpath/setpoint noResponse ${ns}::noResponse
|
|
hsetprop $scobj_hpath/setpoint oldval UNKNOWN
|
|
hsetprop $scobj_hpath/setpoint driving 0
|
|
hsetprop $scobj_hpath/setpoint writestatus UNKNOWN
|
|
hsetprop $scobj_hpath/setpoint type drivable
|
|
hsetprop $scobj_hpath/setpoint setpoint_pending 0
|
|
# Drive adapter interface
|
|
hsetprop $scobj_hpath/setpoint checklimits ${ns}::check $scobj_hpath
|
|
hsetprop $scobj_hpath/setpoint checkstatus ${ns}::drivestatus $scobj_hpath
|
|
hsetprop $scobj_hpath/setpoint halt ${ns}::halt $scobj_hpath
|
|
|
|
hfactory $scobj_hpath/overtemp_warnlimit plain user float
|
|
hsetprop $scobj_hpath/overtemp_warnlimit read ${ns}::getValue rdValue "in_sp_03"
|
|
# hsetprop $scobj_hpath/overtemp_warnlimit write ${ns}::setValue noResponse "out_sp_03"
|
|
hsetprop $scobj_hpath/overtemp_warnlimit rdValue ${ns}::rdValue "double"
|
|
hsetprop $scobj_hpath/overtemp_warnlimit noResponse ${ns}::noResponse
|
|
hsetprop $scobj_hpath/overtemp_warnlimit oldval UNKNOWN
|
|
|
|
hfactory $scobj_hpath/subtemp_warnlimit plain user float
|
|
hsetprop $scobj_hpath/subtemp_warnlimit read ${ns}::getValue rdValue "in_sp_04"
|
|
# hsetprop $scobj_hpath/subtemp_warnlimit write ${ns}::setValue noResponse "out_sp_04"
|
|
hsetprop $scobj_hpath/subtemp_warnlimit rdValue ${ns}::rdValue "double"
|
|
hsetprop $scobj_hpath/subtemp_warnlimit noResponse ${ns}::noResponse
|
|
hsetprop $scobj_hpath/subtemp_warnlimit oldval UNKNOWN
|
|
|
|
hfactory $scobj_hpath/sensor plain spy none
|
|
hfactory $scobj_hpath/sensor/value plain internal float
|
|
# Default to bath temperature sensor
|
|
hsetprop $scobj_hpath/sensor/value tempsensor "in_pv_00"
|
|
hsetprop $scobj_hpath/sensor/value read ${ns}::getValue rdValue {[sct tempsensor]}
|
|
hsetprop $scobj_hpath/sensor/value rdValue ${ns}::rdValue "double"
|
|
hsetprop $scobj_hpath/sensor/value oldval UNKNOWN
|
|
hsetprop $scobj_hpath/sensor/value units "C"
|
|
|
|
hfactory $scobj_hpath/sensor/bathtemp plain internal float
|
|
hsetprop $scobj_hpath/sensor/bathtemp read ${ns}::getValue rdValue "in_pv_00"
|
|
hsetprop $scobj_hpath/sensor/bathtemp rdValue ${ns}::rdValue "double"
|
|
hsetprop $scobj_hpath/sensor/bathtemp oldval UNKNOWN
|
|
hsetprop $scobj_hpath/sensor/bathtemp units "C"
|
|
hfactory $scobj_hpath/sensor/start_temperature plain user float
|
|
hfactory $scobj_hpath/sensor/end_temperature plain user float
|
|
# proc ${ns}::OnFirstSave {} [subst -nocommands {
|
|
# hset $scobj_hpath/sensor/start_temperature [hval $scobj_hpath/sensor/value]
|
|
# hset $scobj_hpath/sensor/end_temperature [hval $scobj_hpath/sensor/value]
|
|
# }]
|
|
# proc ${ns}::OnLastSave {} [subst -nocommands {
|
|
# hset $scobj_hpath/sensor/end_temperature [hval $scobj_hpath/sensor/value]
|
|
# }]
|
|
# ::nexus::OnFirstSave ${ns}::OnFirstSave
|
|
# ::nexus::OnLastSave ${ns}::OnLastSave
|
|
|
|
hfactory $scobj_hpath/heating_power_percent plain internal float
|
|
hsetprop $scobj_hpath/heating_power_percent read ${ns}::getValue rdValue "in_pv_01"
|
|
hsetprop $scobj_hpath/heating_power_percent rdValue ${ns}::rdValue
|
|
hsetprop $scobj_hpath/heating_power_percent oldval UNKNOWN
|
|
|
|
hfactory $scobj_hpath/power plain user int
|
|
hsetprop $scobj_hpath/power read ${ns}::getValue rdValue "in_mode_05"
|
|
hsetprop $scobj_hpath/power write ${ns}::setValue noResponse "out_mode_05"
|
|
hsetprop $scobj_hpath/power rdValue ${ns}::rdPower $scobj_hpath
|
|
hsetprop $scobj_hpath/power noResponse ${ns}::noResponse
|
|
hsetprop $scobj_hpath/power oldval UNKNOWN
|
|
hsetprop $scobj_hpath/power values 0,1
|
|
|
|
hfactory $scobj_hpath/apply_tolerance plain user int
|
|
hsetprop $scobj_hpath/apply_tolerance values 0,1
|
|
hset $scobj_hpath/apply_tolerance 1
|
|
|
|
hfactory $scobj_hpath/tolerance plain user float
|
|
hsetprop $scobj_hpath/tolerance units "C"
|
|
hfactory $scobj_hpath/tolerance/settletime plain user float
|
|
hset $scobj_hpath/tolerance/settletime 60.0
|
|
hsetprop $scobj_hpath/tolerance/settletime units "s"
|
|
hset $scobj_hpath/tolerance $tol
|
|
|
|
hfactory $scobj_hpath/status plain spy text
|
|
hset $scobj_hpath/status "idle"
|
|
hsetprop $scobj_hpath/status values busy,idle
|
|
|
|
hfactory $scobj_hpath/lh45_state plain spy text
|
|
hsetprop $scobj_hpath/lh45_state read ${ns}::getState $scobj_hpath rdState "status"
|
|
hsetprop $scobj_hpath/lh45_state rdState ${ns}::rdState $scobj_hpath
|
|
hsetprop $scobj_hpath/lh45_state oldval UNKNOWN
|
|
|
|
hfactory $scobj_hpath/remote_ctrl plain spy text
|
|
hset $scobj_hpath/remote_ctrl UNKNOWN
|
|
|
|
hfactory $scobj_hpath/lh45_lasterror plain user text
|
|
hset $scobj_hpath/lh45_lasterror ""
|
|
|
|
hfactory $scobj_hpath/lowerlimit plain mugger float
|
|
hsetprop $scobj_hpath/lowerlimit units "C"
|
|
hset $scobj_hpath/lowerlimit -45
|
|
|
|
hfactory $scobj_hpath/upperlimit plain mugger float
|
|
hsetprop $scobj_hpath/upperlimit units "C"
|
|
hset $scobj_hpath/upperlimit 130
|
|
|
|
hfactory $scobj_hpath/emon plain spy none
|
|
hfactory $scobj_hpath/emon/monmode plain user text
|
|
hsetprop $scobj_hpath/emon/monmode values idle,drive,monitor,error
|
|
hset $scobj_hpath/emon/monmode "idle"
|
|
hfactory $scobj_hpath/emon/isintol plain user int
|
|
hset $scobj_hpath/emon/isintol 1
|
|
hfactory $scobj_hpath/emon/errhandler plain user text
|
|
hset $scobj_hpath/emon/errhandler "lazy"
|
|
|
|
if {[SplitReply [environment_simulation]]=="false"} {
|
|
$sct_controller poll $scobj_hpath/setpoint
|
|
$sct_controller write $scobj_hpath/setpoint
|
|
$sct_controller poll $scobj_hpath/subtemp_warnlimit
|
|
# $sct_controller write $scobj_hpath/subtemp_warnlimit
|
|
$sct_controller poll $scobj_hpath/overtemp_warnlimit
|
|
# $sct_controller write $scobj_hpath/overtemp_warnlimit
|
|
$sct_controller poll $scobj_hpath/heating_power_percent
|
|
$sct_controller poll $scobj_hpath/power
|
|
$sct_controller write $scobj_hpath/power
|
|
$sct_controller poll $scobj_hpath/sensor/value
|
|
$sct_controller poll $scobj_hpath/sensor/bathtemp
|
|
$sct_controller poll $scobj_hpath/lh45_state 5 halt read
|
|
}
|
|
|
|
foreach {rootpath hpath klass priv} "
|
|
$scobj_hpath sensor NXsensor spy
|
|
$scobj_hpath sensor/value sensor user
|
|
" {
|
|
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
|
|
hsetprop $scobj_hpath/sensor/value nxalias tc1_sensor_value
|
|
hsetprop $scobj_hpath/sensor/value mutable true
|
|
hsetprop $scobj_hpath/sensor/value sdsinfo ::nexus::scobj::sdsinfo
|
|
|
|
hsetprop $scobj_hpath privilege spy
|
|
::scobj::hinitprops $tempobj setpoint sensor/start_temperature sensor/end_temperature
|
|
hsetprop $scobj_hpath/sensor/start_temperature mutable true
|
|
hsetprop $scobj_hpath/sensor/end_temperature mutable true
|
|
if {[SplitReply [environment_simulation]]=="false"} {
|
|
ansto_makesctdrive ${tempobj}_driveable $scobj_hpath/setpoint $scobj_hpath/sensor/value $sct_controller
|
|
}
|
|
} message ]} {
|
|
return -code error $message
|
|
}
|
|
}
|
|
namespace export mk_sct_julabo_lh45
|
|
}
|
|
|
|
##
|
|
# @brief Create a Julabo lh45 temperature controller
|
|
#
|
|
# @param name, then name of the temperature controller (eg tc1)
|
|
# @param IP, the IP address of the device, this can be a hostname, (eg ca5-quokka)
|
|
# @param port, the IP protocol port number of the device
|
|
# @param sensor (optional), select the 'bath' sensor or an external sensor 'ext'
|
|
# @param _tol (optional), this is the initial tolerance setting
|
|
proc add_lh45 {name IP port {sensor "bath"} {_tol 5.0}} {
|
|
if {[SplitReply [environment_simulation]]=="false"} {
|
|
makesctcontroller sct_lh45 std ${IP}:$port "\r"
|
|
}
|
|
mk_sct_julabo_lh45 sct_lh45 environment $name $_tol
|
|
set scobj_hpath /sics/$name
|
|
switch $sensor {
|
|
"bath" {
|
|
hsetprop $scobj_hpath/sensor/value tempsensor "in_pv_00"
|
|
}
|
|
"ext" {
|
|
hsetprop $scobj_hpath/sensor/value tempsensor "in_pv_02"
|
|
}
|
|
"default" {
|
|
return -code error "ERROR: sensor should be 'bath' or 'ext'"
|
|
}
|
|
}
|
|
makesctemon $name /sics/$name/emon/monmode /sics/$name/emon/isintol /sics/$name/emon/errhandler
|
|
}
|
|
|
|
namespace import ::scobj::lh45::*
|