Files
sics/site_ansto/instrument/config/environment/temperature/sct_watlow_rm.tcl
Ferdi Franceschini d7acb7c16c Load drivers which have been enabled in the SICS config ini files.
Lakeshore 336 drivers with known IP addresses have been added to the ini files with unique IDs.
All entries in the ini files now have unique IDs
The wombat ini now has radio buttons to select sample stage motor configurations.
2014-05-05 12:25:00 +10:00

595 lines
22 KiB
Tcl

# Temperature Controller driver - Watlow RM
# Author Douglas Clowes (dcl@ansto.gov.au)
# Based on the Watlow PM driver
# 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::watlow_rm {
# Temperature controllers must have at least the following nodes
# /tempcont/setpoint
# /tempcont/sensor/value
proc debug_log {args} {
set fd [open "/tmp/watlow_rm.log" a]
puts $fd "[clock format [clock seconds] -format "%T"] $args"
close $fd
}
proc f_to_c { f_temp } {
return [expr ($f_temp - 32.0) * (5.0 / 9.0)]
}
proc c_to_f { c_temp } {
return [expr $c_temp * (9.0 / 5.0) + 32.0]
}
# issue a command to read a register and expect a value response
proc getValue {tc_root nextState cmd} {
set dev "[hval $tc_root/dev_id]"
sct send "$dev:3:$cmd"
return $nextState
}
# issue a command with a value in the target property of the variable
proc setValue {tc_root nextState cmd} {
set dev "[hval $tc_root/dev_id]"
set par [sct target]
sct send "$dev:16:$cmd $par"
debug_log "setValue $dev:16:$cmd $par"
return $nextState
}
proc rdValue {tc_root} {
set data [sct result]
switch -glob -- $data {
"ASCERR:*" {
sct geterror $data
}
default {
if { [hpropexists [sct] geterror] } {
hdelprop [sct] geterror
}
if {$data != [sct oldval]} {
sct oldval $data
sct update $data
sct utime readtime
}
}
}
return idle
}
# write a floating point value
proc setFloat {tc_root nextState cmd} {
set dev "[hval $tc_root/dev_id]"
set par [sct target]
sct send "$dev:1016:$cmd $par"
debug_log "setFloat $dev:1016:$cmd $par"
return $nextState
}
# request a floating point value
proc getFloat {tc_root nextState cmd} {
set dev "[hval $tc_root/dev_id]"
sct send "$dev:1003:$cmd"
return $nextState
}
# read a floating point value
proc rdFloat {tc_root} {
set data [sct result]
switch -glob -- $data {
"ASCERR:*" {
sct geterror $data
}
default {
if { [hpropexists [sct] geterror] } {
hdelprop [sct] geterror
}
if {$data != [sct oldval]} {
sct oldval $data
sct update $data
sct utime readtime
}
}
}
return idle
}
# write a floating point value as a temperature
proc setTemp {tc_root nextState cmd} {
set dev "[hval $tc_root/dev_id]"
set par [sct target]
#set par [c_to_f $par]
sct send "$dev:1016:$cmd $par"
debug_log "setTemp $dev:1016:$cmd $par"
return $nextState
}
# request a floating point value as a temperature
proc getTemp {tc_root nextState cmd} {
set dev "[hval $tc_root/dev_id]"
sct send "$dev:1003:$cmd"
return $nextState
}
# read a floating point value as a temperature
proc rdTemp {tc_root} {
set data [sct result]
#set data [f_to_c $data]
switch -glob -- $data {
"ASCERR:*" {
sct geterror $data
}
default {
if { [hpropexists [sct] geterror] } {
hdelprop [sct] geterror
}
if {$data != [sct oldval]} {
sct oldval $data
sct update $data
sct utime readtime
}
}
}
return idle
}
proc getState {tc_root nextState cmd} {
set dev "[hval $tc_root/dev_id]"
sct send "$dev:3:$cmd"
return $nextState
}
##
# @brief Reads the current watlow state and error messages.
proc rdState {tc_root} {
set my_driving [SplitReply [hgetprop $tc_root/setpoint driving]]
debug_log "rdState $tc_root: driving=$my_driving"
set val [hval $tc_root/setpoint]
debug_log "rdState $tc_root: setpoint=$val"
if {[hpropexists $tc_root/setpoint target]} {
set tgt [SplitReply [hgetprop $tc_root/setpoint target]]
debug_log "rdState $tc_root: target=$tgt"
} else {
hsetprop $tc_root/setpoint target $val
set tgt [SplitReply [hgetprop $tc_root/setpoint target]]
debug_log "rdState $tc_root: initialised target to: target=$tgt"
}
if {$my_driving > 0} {
set temp [hval $tc_root/sensor/value]
set tol [hval $tc_root/tolerance]
set lotemp [expr {$tgt - $tol}]
set hitemp [expr {$tgt + $tol}]
debug_log "rdState driving $tc_root until $temp in ($lotemp, $hitemp)"
if {$temp < $lotemp} {
} elseif {$temp > $hitemp} {
} else {
hset $tc_root/status "idle"
hsetprop $tc_root/setpoint driving 0
}
} else {
if {[hval $tc_root/status] != "idle"} {
hset $tc_root/status "idle"
}
}
set data [SplitReply [hgetprop $tc_root/setpoint driving]]
debug_log "rdState $tc_root: result=$data"
if {[string first "ASCERR:" $data] >=0} {
sct geterror $data
} elseif {$data != [sct oldval]} {
hdelprop [sct] geterror
sct oldval $data
sct update $data
sct utime readtime
}
return idle
}
proc getHP {tc_root nextState cmd} {
set data [hval $tc_root/Loop1/power]
if {$data != [sct oldval]} {
sct oldval $data
sct update $data
sct utime readtime
}
debug_log "getHP $tc_root $nextState $cmd [sct] $data"
return idle
}
proc getPV {tc_root nextState cmd} {
set data [hval $tc_root/Loop1/sensor]
if {$data != [sct oldval]} {
sct oldval $data
sct update $data
sct utime readtime
}
debug_log "getPV $tc_root $nextState $cmd [sct] $data"
return idle
}
proc getSP {tc_root nextState cmd} {
set data [hval $tc_root/Loop1/setpoint]
if {$data != [sct oldval]} {
sct oldval $data
sct update $data
sct utime readtime
}
debug_log "getSP $tc_root $nextState $cmd [sct] $data"
return idle
}
proc setSP {tc_root nextState cmd} {
debug_log "setSP $tc_root $nextState $cmd [sct]=[sct target] [hget [sct]]"
debug_log "setSP sct = [sct], sct writestatus = [sct writestatus]"
if {[sct writestatus] == "start"} {
# Called by drive adapter
hset $tc_root/status "busy"
debug_log "setSP hsetprop $tc_root/setpoint driving 1"
hsetprop $tc_root/setpoint driving 1
}
hset $tc_root/Loop1/setpoint [sct target]
return idle
}
proc setPoint {tc_root nextState cmd} {
set dev "[hval $tc_root/dev_id]"
set par [sct target]
if {[sct writestatus] == "start"} {
# Called by drive adapter
hset $tc_root/status "busy"
hsetprop $tc_root/setpoint driving 1
}
set par [c_to_f $par]
sct send "$dev:1016:$cmd $par"
debug_log "setPoint $dev:1016:$cmd $par"
return $nextState
}
proc noResponse {} {
return idle
}
proc wrtValue {wcmd args} {
}
# check that a target is within allowable limits
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} {
error "setpoint violates limits"
}
return OK
}
# Check that the sensor is reading within tolerance of the setpoint.
# Return 1 or 0 if it is or is not, respectively.
proc checktol {tc_root currtime timecheck} {
debug_log "checktol $tc_root $currtime $timecheck"
set temp [hval $tc_root/sensor/value]
set lotemp [hval $tc_root/subtemp_warnlimit]
set hitemp [hval $tc_root/overtemp_warnlimit]
if { $temp < $lotemp || $temp > $hitemp} {
hset $tc_root/emon/isintol 0
return 0
} else {
set timeout [hval $tc_root/tolerance/settletime]
if { ($currtime - $timecheck) > $timeout } {
hset $tc_root/emon/isintol 1
}
return 1
}
}
##
# @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 {
sct print "drivestatus: idle"
return idle
}
}
proc halt {tc_root} {
debug_log "halt $tc_root"
hset $tc_root/setpoint [hval $tc_root/sensor/value]
hsetprop $tc_root/setpoint driving 0
return idle
}
##
# @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 scriptcontext object (typically sct_xxx_yyy)
# @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 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\
drivable dataType permission rdCmd rdFunc wrCmd\
wrFunc allowedValues klass} {
set catch_status [ catch {
# set ns ::scobj::ls460
set ns "[namespace current]"
set nodeName "$scobj_hpath/$cmdGroup/$varName"
if {1 > [string length $cmdGroup]} {
set nodeName "$scobj_hpath/$varName"
}
debug_log "Creating node $nodeName"
hfactory $nodeName plain $permission $dataType
if {$readable > 0} {
set parts [split "$rdFunc" "."]
if { [llength $parts] == 2 } {
set func_name [lindex $parts 0]
set next_state [lindex $parts 1]
hsetprop $nodeName read ${ns}::$func_name $scobj_hpath $next_state $rdCmd
hsetprop $nodeName $next_state ${ns}::$next_state $scobj_hpath
} else {
if {$rdFunc == "getPV"} {
set func_name "$rdFunc"
} elseif {$rdFunc == "getSP"} {
set func_name "$rdFunc"
} elseif {$rdFunc == "getHP"} {
set func_name "$rdFunc"
} elseif {$rdFunc == "rdFloat"} {
set func_name "getFloat"
} elseif {$rdFunc == "rdTemp"} {
set func_name "getTemp"
} else {
set func_name "getValue"
}
hsetprop $nodeName read ${ns}::$func_name $scobj_hpath $rdFunc $rdCmd
hsetprop $nodeName $rdFunc ${ns}::$rdFunc $scobj_hpath
}
set poll_period 30
if { $readable >= 0 && $readable <= 9 } {
set poll_period [lindex [list 0 1 2 3 4 5 10 15 20 30] $readable]
}
debug_log "Registering node $nodeName for poll at $poll_period seconds"
$sct_controller poll $nodeName $poll_period
}
if {$writable == 1} {
set parts [split "$wrFunc" "."]
if { [llength $parts] == 2 } {
set func_name [lindex $parts 0]
set next_state [lindex $parts 1]
hsetprop $nodeName write ${ns}::$func_name $scobj_hpath $next_state $wrCmd
hsetprop $nodeName $next_state ${ns}::$next_state $scobj_hpath
} else {
hsetprop $nodeName write ${ns}::$wrFunc $scobj_hpath noResponse $wrCmd
hsetprop $nodeName noResponse ${ns}::noResponse
}
hsetprop $nodeName writestatus UNKNOWN
debug_log "Registering node $nodeName for write callback"
$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
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 ]
if {$catch_status != 0} {
return -code error "in createNode $message"
}
return OK
}
proc mk_sct_watlow_rm {sct_controller klass tempobj dev_id tol CID CTYPE} {
set catch_status [ catch {
set ns "[namespace current]"
MakeSICSObj $tempobj SCT_OBJECT
sicslist setatt $tempobj klass $klass
sicslist setatt $tempobj long_name $tempobj
set scobj_hpath /sics/$tempobj
#
# These addresses are modbus addresses on the RUI/gateway over two devices
# The RMC is at address 1 and the modbus offset on the RUI for gateway 1
# is zero, so these addresses are as specified in the manual.
# The RMS is at address 2 and the modbus offset on the RUI for gateway 1
# is 5000, so these addresses have 5000 added to those specified in the manual.
#
set deviceCommand {\
{} setpoint 1 1 1 float user {0} {getSP} {0} {setSP} {}\
sensor value 1 0 0 float internal {0} {getPV} {0} {} {}\
{} power 1 0 0 float internal {0} {getHP} {0} {} {}\
Loop1 setpoint 1 1 0 float user {2500} {rdTemp} {2500} {setTemp} {}\
Loop1 sensor 1 0 0 float internal {3430} {rdTemp} {3430} {} {}\
Loop1 PV 1 0 0 float internal {3442} {rdTemp} {3442} {} {}\
Loop1 power 1 0 0 float internal {2248} {rdFloat} {2248} {} {}\
Loop1 sensorerror 1 0 0 int internal {3452} {rdValue} {3452} {} {}\
Loop1 looperror 1 0 0 int internal {2268} {rdValue} {2268} {} {}\
Analog Input01 1 0 0 float internal {360} {rdTemp} {360} {} {}\
Analog Input02 1 0 0 float internal {450} {rdTemp} {450} {} {}\
Analog Input03 1 0 0 float internal {5380} {rdTemp} {5380} {} {}\
Analog Input04 1 0 0 float internal {5470} {rdTemp} {5470} {} {}\
Analog Input05 1 0 0 float internal {5560} {rdTemp} {5560} {} {}\
Analog Input06 1 0 0 float internal {5650} {rdTemp} {5650} {} {}\
Analog Input07 1 0 0 float internal {5740} {rdTemp} {5740} {} {}\
Analog Input08 1 0 0 float internal {5830} {rdTemp} {5830} {} {}\
Analog Input09 1 0 0 float internal {5920} {rdTemp} {5920} {} {}\
Analog Input10 1 0 0 float internal {6010} {rdTemp} {6010} {} {}\
}
hfactory $scobj_hpath/sensor plain spy none
hfactory $scobj_hpath/Analog plain spy none
hfactory $scobj_hpath/Loop1 plain spy none
foreach {cmdGroup varName readable writable drivable dataType permission rdCmd rdFunc wrCmd wrFunc allowedValues} $deviceCommand {
createNode $scobj_hpath $sct_controller $cmdGroup $varName $readable $writable $drivable $dataType $permission $rdCmd $rdFunc $wrCmd $wrFunc $allowedValues $klass
}
hsetprop $scobj_hpath/sensor/value lowerlimit 0
hsetprop $scobj_hpath/sensor/value upperlimit 500
hsetprop $scobj_hpath/sensor/value units "C"
hsetprop $scobj_hpath/sensor/value permlink data_set ${CTYPE}${CID}S1
hsetprop $scobj_hpath/setpoint permlink data_set ${CTYPE}${CID}SP1
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/dev_id plain user int
hsetprop $scobj_hpath/dev_id values 0,1,2,3,4,5,6,7,8,9
hset $scobj_hpath/dev_id $dev_id
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 5.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/device_state plain spy text
hsetprop $scobj_hpath/device_state read ${ns}::getState $scobj_hpath rdState "362"
hsetprop $scobj_hpath/device_state rdState ${ns}::rdState $scobj_hpath
hsetprop $scobj_hpath/device_state oldval UNKNOWN
hfactory $scobj_hpath/remote_ctrl plain spy text
hset $scobj_hpath/remote_ctrl UNKNOWN
hfactory $scobj_hpath/device_lasterror plain user text
hset $scobj_hpath/device_lasterror ""
hfactory $scobj_hpath/lowerlimit plain mugger float
hsetprop $scobj_hpath/lowerlimit units "C"
hset $scobj_hpath/lowerlimit 0
hfactory $scobj_hpath/upperlimit plain mugger float
hsetprop $scobj_hpath/upperlimit units "C"
hset $scobj_hpath/upperlimit 500
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 "pause"
if {[SplitReply [environment_simulation]]=="false"} {
$sct_controller poll $scobj_hpath/device_state 1 halt read
}
::scobj::hinitprops $tempobj
hsetprop $scobj_hpath klass environment
::scobj::set_required_props $scobj_hpath
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
hsetprop $scobj_hpath/setpoint data true
hsetprop $scobj_hpath/setpoint type drivable
if {[SplitReply [environment_simulation]]=="false"} {
ansto_makesctdrive ${tempobj}_driveable $scobj_hpath/setpoint $scobj_hpath/sensor/value $sct_controller
}
} catch_message ]
if {$catch_status != 0} {
return -code error $catch_message
}
}
namespace export mk_sct_watlow_rm
}
##
# @brief Create a Watlow RM temperature controller
#
# @param name, the name of the temperature controller (eg tc1)
# @param IP, the IP address of the device, this can be a hostname, (eg ca5-kowari)
# @param port, the IP protocol port number of the device (502 for modbus)
# @param _tol (optional), this is the initial tolerance setting
proc add_watlow_rm {name IP port dev_id {_tol 5.0} {CID 1} {CTYPE T}} {
set fd [open "/tmp/watlow_rm.log" a]
if {[SplitReply [environment_simulation]]=="false"} {
puts $fd "makesctcontroller sct_${name} modbus ${IP}:$port"
makesctcontroller sct_${name} modbus ${IP}:$port
}
puts $fd "mk_sct_watlow_rm sct_${name} environment $name $dev_id $_tol"
mk_sct_watlow_rm sct_${name} environment $name $dev_id $_tol $CID $CTYPE
puts $fd "makesctemon $name /sics/$name/emon/monmode /sics/$name/emon/isintol /sics/$name/emon/errhandler"
makesctemon $name /sics/$name/emon/monmode /sics/$name/emon/isintol /sics/$name/emon/errhandler
close $fd
}
puts stdout "file evaluation of sct_watlow_rm.tcl"
set fd [open "/tmp/watlow_rm.log" w]
puts $fd "file evaluation of sct_watlow_rm.tcl"
close $fd
if {[ catch {
if { [ info exists ::config_dict ] } {
dict for {secname secinfo} $::config_dict {
if { [dict exists $secinfo "driver"] && ([dict get $secinfo "driver"] == "watlow_rm") } {
if { [ dict get $::secinfo enabled ] } {
set name [dict get $::secinfo name]
set IP [dict get $::secinfo ip]
set PORT [dict get $::secinfo port]
set sensor [dict get $::secinfo sensor]
set tol [dict get $::secinfo tol]
set cid [dict get $::secinfo id]
set ctype [dict get $::secinfo type]
add_watlow_rm $name $IP $PORT $devid $tol $cid $ctype
}
}
}
}
} message ]} {
puts "ERROR: $message"
}
namespace import ::scobj::watlow_rm::*