# Define procs in ::scobj::xxx namespace # MakeSICSObj $obj SCT_ # 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 } set tempcmd [sct tempsensor] sct send $tempcmd 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 CID CTYPE} { 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 hsetprop $scobj_hpath/setpoint permlink data_set ${CTYPE}${CID}SP1 # 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}::getTemp rdValue hsetprop $scobj_hpath/sensor/value rdValue ${ns}::rdValue "double" hsetprop $scobj_hpath/sensor/value oldval UNKNOWN hsetprop $scobj_hpath/sensor/value units "C" hsetprop $scobj_hpath/sensor/value permlink data_set ${CTYPE}${CID}S1 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 } namespace import ::scobj::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_julabo_lh45 { name IP port {sensor "bath"} {_tol 5.0} {CID 1} {CTYPE T} } { if {[SplitReply [environment_simulation]]=="false"} { if {[string equal -nocase "aqadapter" "${IP}"]} { ::scobj::julabo_lh45::sics_log 9 "makesctcontroller sct_${name} aqadapter ${port}" makesctcontroller sct_${name} aqadapter ${port} } else { makesctcontroller sct_${name} std ${IP}:$port "\r" } } mk_sct_julabo_lh45 sct_${name} environment $name $_tol $CID $CTYPE 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 }