1162 lines
28 KiB
Tcl
1162 lines
28 KiB
Tcl
namespace eval ccu {
|
|
variable bits
|
|
variable pcnt 0
|
|
# bits to be kept / bits to be set
|
|
array set bits {
|
|
pulsOn {0xfe 1}
|
|
pulsOff {0xfe 0}
|
|
n2On {0xfc 1}
|
|
n2Off {0xfc 2}
|
|
heOn {0xfa 1}
|
|
heOff {0xfa 4}
|
|
nvOpen {0xf6 9}
|
|
nvClose {0xf6 1}
|
|
nvOff {0x0e 0xc0}
|
|
weak {0xcf 0x20}
|
|
normal {0xcf 0x10}
|
|
strong {0xcf 0x30}
|
|
micro {0x3f 0xc0}
|
|
short {0x3f 0x80}
|
|
long {0x3f 0x00}
|
|
}
|
|
}
|
|
|
|
# general ccu initialization
|
|
proc stdConfig::ccuInit {} {
|
|
variable node
|
|
variable ctrl
|
|
variable name
|
|
|
|
if {[controller std "0x0d" 2]} {
|
|
controllerDesc "CCU cryostat control unit"
|
|
prop resolution 26.214
|
|
prop dout 0x06
|
|
prop readA0 0
|
|
prop readA1 0
|
|
prop readA2 0
|
|
prop readA3 0
|
|
prop readA4 0
|
|
prop readA5 0
|
|
prop readA6 0
|
|
prop readA7 0
|
|
prop starttime 0
|
|
prop n2off ccu::valveoff n2
|
|
prop n2on ccu::valveon n2
|
|
prop heoff ccu::valveoff he
|
|
prop heon ccu::valveon he
|
|
prop beforefixed 0
|
|
set node /sics/$ctrl/tasks
|
|
poll 1
|
|
prop start ccu::start
|
|
prop read ccu::handler
|
|
}
|
|
set node /sics/$ctrl
|
|
}
|
|
|
|
# automatic n2 fill with two pt sensors
|
|
proc stdConfig::n2_ccu {{title ""} {type n2cryo}} {
|
|
variable name
|
|
|
|
ccuInit
|
|
# A0: upper sensor, A1: lower sensor
|
|
prop readA0 1
|
|
prop readA1 1
|
|
prop n2path /$name
|
|
if {$type eq "trap"} {
|
|
prop warn_trap_empty 1
|
|
set vis false
|
|
} else {
|
|
prop warn_no_auto 1
|
|
set vis true
|
|
}
|
|
|
|
obj n2ccu out -int
|
|
default -1
|
|
prop write ccu::write
|
|
|
|
prop enum watching=0,filling=1,inactive=4
|
|
prop visible $vis
|
|
prop label "filling state"
|
|
|
|
if {$title eq ""} {
|
|
set title "LN2 fill settings"
|
|
}
|
|
|
|
kids $title {
|
|
|
|
node threshold par 90
|
|
prop help {[K] a sensor below this value is immersed}
|
|
prop visible $vis
|
|
|
|
node limit par 240
|
|
prop label "warm limit"
|
|
prop help {[K] above this value the cryo is considered as warm}
|
|
prop visible $vis
|
|
|
|
node tmo1 par 300
|
|
prop label "max. tube cooling time"
|
|
prop help {[sec]}
|
|
prop visible $vis
|
|
|
|
node tmo2 par 1800
|
|
prop label "max. fill time"
|
|
prop help {[sec]}
|
|
prop visible $vis
|
|
|
|
node upper upd
|
|
node lower upd
|
|
}
|
|
return "LN2 fill with CCU"
|
|
}
|
|
|
|
# automatic fill with level reading from external source
|
|
proc stdConfig::ccuExtInit {type {readlevel 0}} {
|
|
|
|
variable node
|
|
variable ctrl
|
|
|
|
ccuInit
|
|
|
|
obj fillccu out -int
|
|
default -1
|
|
prop write ccu::writeExt $type
|
|
|
|
prop enum watching=0,filling=1,inactive=2
|
|
prop label "filling state"
|
|
|
|
if {$type eq "n2"} {
|
|
set text LN2
|
|
} else {
|
|
set text LHe
|
|
}
|
|
kids "$text fill settings" {
|
|
|
|
node readlevel -text par $readlevel
|
|
prop visible false
|
|
node lowlevel par 10
|
|
node highlevel par 100
|
|
|
|
node smooth upd
|
|
prop fmt %.7g
|
|
default -1
|
|
|
|
node minfillminutes par 3.0
|
|
node maxfillminutes par 30.0
|
|
node minholdhours par 12.0
|
|
node maxholdhours par 120.0
|
|
node tolerance par 20.0
|
|
node badreadingminutes par 30.0
|
|
node tubecoolingminutes par 3.0
|
|
if {$type eq "he"} {
|
|
node vessellimit par 10.0
|
|
node vext upd
|
|
}
|
|
}
|
|
set node /sics/$ctrl
|
|
}
|
|
|
|
proc stdConfig::n2_ext_ccu {} {
|
|
variable name
|
|
|
|
ccuExtInit n2 "lev n2"
|
|
prop n2extpath /$name
|
|
prop n2fill "ccu::fillExt n2"
|
|
return "LN2 fill with CCU and external level meter"
|
|
}
|
|
|
|
proc stdConfig::he_ext_ccu {{readlevel lev}} {
|
|
variable name
|
|
|
|
ccuExtInit he $readlevel
|
|
prop heextpath /$name
|
|
prop hefill "ccu::fillExt he"
|
|
return "LHe fill with CCU and external level meter"
|
|
}
|
|
|
|
proc stdConfig::he_ccu {} {
|
|
variable name
|
|
|
|
ccuInit
|
|
# A3: he sensor
|
|
prop readA3 1
|
|
prop hepath /$name
|
|
# voltage divider 56.2 kOhm + 200 kOhm, max. 10 V
|
|
# full is ~ 2.2 V
|
|
prop full [expr 56.2 / 256.2 * 10]
|
|
prop empty 0
|
|
|
|
obj heccu upd
|
|
return "He reading from AMI135/136 with CCU"
|
|
}
|
|
|
|
proc stdConfig::he9_ccu {} {
|
|
variable name
|
|
|
|
ccuInit
|
|
# A6: he sensor (potentiometer channel)
|
|
prop readA6 2
|
|
prop hepath /$name
|
|
# on this level meter, full is 0, empty is 0.5 V
|
|
prop full 0
|
|
prop empty 0.5
|
|
|
|
obj heccu upd
|
|
return "He reading from old MA09 with CCU"
|
|
}
|
|
|
|
proc stdConfig::n2cool_ccu {} {
|
|
variable name
|
|
variable node
|
|
variable ctrl
|
|
|
|
ccuInit
|
|
|
|
obj fillccu out -int
|
|
default -1
|
|
prop write ccu::writePrecool
|
|
|
|
prop enum watching=0,filling=1,inactive=2
|
|
prop label "precool filling state"
|
|
|
|
return "LN2 precool with CCU"
|
|
}
|
|
|
|
proc stdConfig::nv_ccu {{vtype VTI}} {
|
|
variable name
|
|
|
|
ccuInit
|
|
# A2: pressure sensor
|
|
prop readA2 1
|
|
prop nvpath /$name
|
|
|
|
obj nvccu out -int
|
|
default 0
|
|
prop control ccu::control
|
|
prop write ccu::writeNv
|
|
if {$vtype != "VTI"} {
|
|
# lambda point refrigerator
|
|
prop enum "off=5,fixed=0,controlling=1,close=3,open=4"
|
|
prop vtype $vtype
|
|
} else {
|
|
prop enum "automatic=2,fixed=0,controlling=1,close=3,open=4"
|
|
prop vtype $vtype
|
|
}
|
|
poll 0.5 progress ticker
|
|
prop ticker ccu::ticker
|
|
|
|
kids "$vtype needle valve settings" {
|
|
node set out
|
|
default 2
|
|
prop write ccu::writeSet
|
|
prop label "flow set"
|
|
|
|
node flowmax par 20
|
|
prop label "flow maximum"
|
|
|
|
node prop par 200
|
|
|
|
node int par 10
|
|
|
|
node minpulse par 100
|
|
prop label "minimum pulse length:"
|
|
prop enum "micro=1,short=10,long=100"
|
|
prop help "micro: 1 ms, short: 10 ms, long: 100 ms"
|
|
|
|
node tolerance par 0.05
|
|
|
|
node speed upd
|
|
|
|
node flow upd
|
|
|
|
node pos upd
|
|
default 20
|
|
|
|
kids "position estimation parameters" {
|
|
node norm par 20
|
|
node max par 60
|
|
}
|
|
|
|
node autoflow -none
|
|
kids "autoflow control parameters" {
|
|
flow::make
|
|
}
|
|
}
|
|
return "$vtype needle valve control with CCU"
|
|
}
|
|
|
|
proc stdConfig::nvread_ccu {} {
|
|
variable name
|
|
|
|
ccuInit
|
|
# A2: pressure sensor
|
|
prop readA2 1
|
|
prop nvpath /$name
|
|
prop readonly 1
|
|
|
|
obj nvccu rd -int
|
|
prop read ccu::flowtask
|
|
prop enum "readonly=0"
|
|
|
|
kids "pressure reading" {
|
|
node flow upd
|
|
}
|
|
}
|
|
|
|
proc ccu::flowtask {} {
|
|
sct update 0
|
|
namespace eval :: {
|
|
flowtask
|
|
}
|
|
return idle
|
|
}
|
|
|
|
proc ccu::start {} {
|
|
# set all digital ports to output push-pull and all outputs inactive
|
|
# remark: bit 1 and 2 are inactive High (hex 06)
|
|
sct send ":10000000030600FF00FF0006.."
|
|
return ccu::setADCrate
|
|
}
|
|
|
|
proc ccu::setADCrate {} {
|
|
# set adc rate to 8
|
|
sct send ":10000D0001020008.."
|
|
return ccu::getidn
|
|
}
|
|
|
|
proc ccu::getidn {} {
|
|
sct send ":0300030001.."
|
|
return stdSct::completeStart
|
|
}
|
|
|
|
proc ccu::handler {} {
|
|
variable last_nv 0
|
|
variable last_n2 0
|
|
|
|
set now [clock seconds]
|
|
if {$now/5 != $last_n2/5} {
|
|
set last_n2 $now
|
|
# read all used channels
|
|
set chanlist {0 1 2 3 4 5 6 7}
|
|
} elseif {[sct readA2]} {
|
|
set last_nv $now
|
|
# read only used channels for needle valve control
|
|
set chanlist {2 6 7}
|
|
}
|
|
set min 8
|
|
set max -1
|
|
foreach chan $chanlist {
|
|
if {[sct readA$chan]} {
|
|
if {$chan > $max} {
|
|
set max $chan
|
|
}
|
|
if {$chan < $min} {
|
|
set min $chan
|
|
}
|
|
}
|
|
}
|
|
if {[silent 0 sct hefill] ne 0} {
|
|
[sct controllerName] queue [sct heextpath] read hefill
|
|
}
|
|
if {[silent 0 sct n2fill] ne 0} {
|
|
[sct controllerName] queue [sct n2extpath] read n2fill
|
|
}
|
|
if {$min > $max} {
|
|
return idle
|
|
}
|
|
|
|
set n [expr $max - $min + 1]
|
|
sct firstChan $min
|
|
sct lastChan $max
|
|
sct nChan $n
|
|
sct send [format ":04%4.4x%4.4x.." $min $n]
|
|
|
|
return ccu::update
|
|
}
|
|
|
|
proc ccu::smooth {var band old} {
|
|
upvar $var val
|
|
#clientput "$val $band $old"
|
|
set weight [expr 0.05 + pow(($val - $old) / $band, 2)]
|
|
#clientput "w $weight"
|
|
if {$weight < 1.0} {
|
|
set val [expr $old + ($val - $old) * $weight]
|
|
}
|
|
#clientput "$weight $val"
|
|
}
|
|
|
|
proc ccu::convertPt2T {sensor raw} {
|
|
set millivolt [expr $raw / [sct resolution]]
|
|
if {$millivolt < 30} {
|
|
hupdate [sct n2path]/status "LN2 sensor unplugged"
|
|
hsetprop [sct n2path]/$sensor geterror "LN2 sensor unplugged"
|
|
return 0
|
|
}
|
|
if {$millivolt < 150 || $millivolt > 1500} {
|
|
hupdate [sct n2path]/status "illegal LN2 sensor reading: $millivolt mV"
|
|
hsetprop [sct n2path]/$sensor geterror "illegal LN2 sensor reading: $millivolt mV"
|
|
return 0
|
|
}
|
|
set t [expr 68 * (5000.0 / $millivolt - 1.0) * 0.25 + 26.8]
|
|
catch { ccu::smooth t 5.0 [hvali [sct n2path]/$sensor] }
|
|
hupdate [sct n2path]/$sensor $t
|
|
hdelprop [sct n2path]/$sensor geterror
|
|
return $t
|
|
}
|
|
|
|
proc ccu::nvstatus {} {
|
|
set status ""
|
|
set vtype [hgetpropval [sct nvpath] vtype]
|
|
if {[silent -63 hvali [sct nvpath]/flow] < -50} {
|
|
append status "$vtype needle valve pressure sensor not connected\n"
|
|
} elseif {[silent -1 hvali [sct nvpath]] == -1} {
|
|
append status "$vtype needle valve fixed\n"
|
|
}
|
|
set pos [silent 20 hvali [sct nvpath]/pos]
|
|
if {$pos >= [hvali [sct nvpath]/pos/max]} {
|
|
append status "$vtype needle valve probably open\n"
|
|
}
|
|
if {$pos <= 0} {
|
|
append status "$vtype needle valve probably closed\n"
|
|
}
|
|
hupdate [sct nvpath]/status $status
|
|
}
|
|
|
|
proc ccu::update {} {
|
|
set fmt [format ":04%2.2X%s" [expr 2 * [sct nChan]] [string repeat %4x [sct nChan]]]
|
|
set vars [lrange {a0 a1 a2 a3 a4 a5 a6 a7} [sct firstChan] [sct lastChan]]
|
|
if {[eval scan [sct result] $fmt $vars] != [sct nChan]} {
|
|
error "bad response to '[sct send]': '[sct result]'"
|
|
}
|
|
if {[sct readA2]} {
|
|
# reading 0..65535, voltage 0..2500 mV with a 100 Ohm resistor: 0..25 mA
|
|
# 4..20 mA is 0..250 mbar
|
|
set flow [expr ($a2 / 65535.0 * 25 - 4) / 16.0 * 250]
|
|
catch { ccu::smooth flow 0.5 [silent 0 hvali [sct nvpath]/flow] }
|
|
hupdate [sct nvpath]/flow $flow
|
|
if {$flow < -50} {
|
|
hsetprop [sct nvpath]/flow geterror "pressure sensor not connected"
|
|
switch [silent 0 hvali [sct nvpath]] {
|
|
1 - 2 {
|
|
hset [sct nvpath] 0
|
|
hsetprop [sct nvpath]/flow geterror "pressure sensor not connected"
|
|
}
|
|
}
|
|
} else {
|
|
hdelprop [sct nvpath]/flow geterror
|
|
}
|
|
if {[silent 0 sct readonly] == 0} {
|
|
ccu::nvstatus
|
|
[sct controllerName] queue [sct nvpath] read control
|
|
}
|
|
}
|
|
if {[sct readA6] == 2} {
|
|
# He reading on chan 6 (for MA09)
|
|
# reading 65535 is 2.5 V, output is in percent
|
|
set val [expr 100.*(2.5*$a6/65535.-[sct empty])/([sct full]-[sct empty])]
|
|
hupdate [sct hepath] $val
|
|
} elseif {[sct readA3]} {
|
|
# He reading on chan 3 (for AMI 135/136 level meter)
|
|
# reading 65535 is 2.5 V, output is in percent
|
|
set val [expr 100.*(2.5*$a3/65535.-[sct empty])/([sct full]-[sct empty])]
|
|
if {$val < -1.0} {
|
|
set val -1.0
|
|
} elseif {$val > 101.0} {
|
|
set val 101.0
|
|
}
|
|
hupdate [sct hepath] $val
|
|
}
|
|
if {[sct firstChan] == 0} {
|
|
# automatic n2 fill with 2 point sensor
|
|
set upper [convertPt2T upper $a0]
|
|
set lower [convertPt2T lower $a1]
|
|
|
|
set lim [hvali [sct n2path]/limit]
|
|
set thr [hvali [sct n2path]/threshold]
|
|
set state [hvali [sct n2path]]
|
|
if {$upper > $lim || $lower > $lim} {
|
|
if {$state < 3} {
|
|
hupdate [sct n2path]/status "sensor warm"
|
|
hupdate [sct n2path] 4
|
|
hsetprop [sct n2path] enum watching=0,fill=3,inactive (sensor warm)=4
|
|
return n2off
|
|
} elseif {$state == 4} {
|
|
hupdate [sct n2path]/status "sensor warm"
|
|
hupdate [sct n2path] $state
|
|
return idle
|
|
}
|
|
} elseif {[string equal "sensor warm" [result hvali [sct n2path]/status]]} {
|
|
hupdate [sct n2path]/status ""
|
|
}
|
|
if {$upper < $thr && $lower > $thr} {
|
|
if {$state < 4} {
|
|
hupdate [sct n2path]/status "sensor upside down"
|
|
hupdate [sct n2path] 4
|
|
hsetprop [sct n2path] enum watching=0,fill=3,inactive (sensor upside down)=4
|
|
return n2off
|
|
} else {
|
|
hupdate [sct n2path] $state
|
|
return idle
|
|
}
|
|
} elseif {[string equal "sensor upside down" [result hvali [sct n2path]/status]]} {
|
|
hupdate [sct n2path]/status ""
|
|
}
|
|
if {[silent 0 sct warn_trap_empty]} {
|
|
set now [clock seconds]
|
|
if {$lower > $thr} {
|
|
if {[silent 0 sct empty_time] == 0} {
|
|
sct empty_time $now
|
|
}
|
|
if {$now > [sct empty_time] + 600} {
|
|
set hr [expr 20 - ($now - [sct empty_time]) / 3600]
|
|
if {$hr <= 0} {
|
|
set txt "the trap gets empty soon - please fill immediately"
|
|
} else {
|
|
set txt "the trap gets empty soon - please fill within $hr h"
|
|
}
|
|
} else {
|
|
set txt ""
|
|
}
|
|
} else {
|
|
set txt ""
|
|
sct empty_time 0
|
|
}
|
|
if {$txt ne [silent 0 hvali [sct n2path]/status]} {
|
|
hupdate [sct n2path]/status $txt
|
|
}
|
|
}
|
|
switch -- $state {
|
|
-1 { # initializing
|
|
hupdate [sct n2path] 4
|
|
hsetprop [sct n2path] enum watching=0,fill=1,inactive=4
|
|
if {[silent 0 sct warn_no_auto]} {
|
|
hupdate [sct n2path]/status "automatic LN2 filling off"
|
|
}
|
|
return n2off
|
|
}
|
|
0 { # watching
|
|
if {$lower > $thr} {
|
|
clientput "start N2 fill"
|
|
hupdate [sct n2path] 1
|
|
hsetprop [sct n2path] enum watching=0,fill (starting)=1,inactive=4
|
|
sct starttime [clock seconds]
|
|
return n2on
|
|
}
|
|
}
|
|
1 { # filling until lower or upper is cold
|
|
if {[clock seconds] > [sct starttime] + [hvali [sct n2path]/tmo1]} {
|
|
if {$lower < $thr} {
|
|
hupdate [sct n2path] 2
|
|
hsetprop [sct n2path] enum watching=0,fill=2,inactive=4
|
|
return n2on
|
|
}
|
|
clientput "stop N2 fill (lower sensor not cold quick enough)"
|
|
hupdate [sct n2path]/status "lower sensor not cold quick enough"
|
|
hupdate [sct n2path] 4
|
|
hsetprop [sct n2path] enum watching=0,fill=3,inactive (lower sensor not cold quick enough)=4
|
|
return n2off
|
|
} else {
|
|
if {$upper < $thr} {
|
|
clientput "finished N2 fill"
|
|
hupdate [sct n2path] 0
|
|
hsetprop [sct n2path] enum watching=0,fill=1,inactive=4
|
|
return n2off
|
|
}
|
|
}
|
|
sct update $state
|
|
return n2on
|
|
}
|
|
2 - 3 { # filling until upper is cold
|
|
if {[clock seconds] > [sct starttime] + [hvali [sct n2path]/tmo2]} {
|
|
clientput "stop N2 fill (fill timeout)"
|
|
hupdate [sct n2path]/status "fill timeout"
|
|
hupdate [sct n2path] 4
|
|
hsetprop [sct n2path] enum watching=0,filling=2,inactive (fill timeout)=4
|
|
return n2off
|
|
} elseif {$upper < $thr} {
|
|
clientput "finished N2 fill"
|
|
hupdate [sct n2path] 0
|
|
hsetprop [sct n2path] enum watching=0,fill=1,inactive=4
|
|
return n2off
|
|
}
|
|
hupdate [sct n2path] $state
|
|
return n2on
|
|
}
|
|
}
|
|
hupdate [sct n2path] $state
|
|
}
|
|
return idle
|
|
}
|
|
|
|
proc ccu::output args {
|
|
variable bits
|
|
variable pcnt
|
|
|
|
set dout [sct dout]
|
|
foreach a $args {
|
|
set b $bits($a)
|
|
set dout [expr $dout & [lindex $b 0] | [lindex $b 1]]
|
|
}
|
|
sct dout $dout
|
|
sct send [format ":10000200010200%2.2x.." $dout]
|
|
if {[silent 0 result pulsedebug]} {
|
|
if {[string equal pulsOn $args]} {
|
|
incr pcnt
|
|
} elseif {[string equal pulsOff $args]} {
|
|
} else {
|
|
clientput "$args ($pcnt)"
|
|
set pcnt 0
|
|
}
|
|
clientput "[format "%2.2x" $dout]"
|
|
}
|
|
}
|
|
|
|
proc ccu::valveoff {type} {
|
|
ccu::output ${type}Off
|
|
return stdSct::complete
|
|
}
|
|
|
|
proc ccu::valveon {type} {
|
|
ccu::output ${type}On
|
|
return ccu::pulse
|
|
}
|
|
|
|
proc ccu::pulse {} {
|
|
ccu::output pulsOff
|
|
return stdSct::complete
|
|
}
|
|
|
|
proc ccu::write {} {
|
|
hupdate [sct n2path]/status ""
|
|
switch -- [sct target] {
|
|
# watching
|
|
0 {
|
|
sct update [sct target]
|
|
sct enum watching=0,fill=3,inactive=4
|
|
return n2off
|
|
}
|
|
# inactive
|
|
4 {
|
|
sct update 4
|
|
sct enum watching=0,fill=3,inactive=4
|
|
hupdate [sct]/status "automatic LN2 filling off"
|
|
return n2off
|
|
}
|
|
1 - 2 - 3 {
|
|
sct starttime [clock seconds]
|
|
if {[hvali [sct]] < 3} {
|
|
sct update 1
|
|
sct enum watching=0,fill=1,inactive=4
|
|
} else {
|
|
sct update 3
|
|
sct enum watching=0,fill=3,inactive=4
|
|
}
|
|
return idle
|
|
}
|
|
}
|
|
return idle
|
|
}
|
|
|
|
# --- automatic fill with level reading from external source ---
|
|
|
|
proc calcsmooth {level minspeed maxspeed} {
|
|
if {![string is double $level]} {
|
|
return 999.9
|
|
}
|
|
set now [clock seconds]
|
|
set delta [expr $now - [silent $now sct lasttime]]
|
|
sct lasttime $now
|
|
# use property for better resolution
|
|
set smooth [hvali [sct]/smooth]
|
|
if {$smooth < 0} {
|
|
set smooth $level
|
|
}
|
|
set gs [expr $smooth + $delta * $maxspeed]
|
|
if {$level > $gs} {
|
|
set smooth $gs
|
|
} else {
|
|
set gs [expr $smooth + $delta * $minspeed]
|
|
if {$level < $gs} {
|
|
set smooth $gs
|
|
} else {
|
|
set smooth $level
|
|
}
|
|
}
|
|
set badtime [silent 0 sct badtime]
|
|
if {abs($smooth - $level) > [hvali [sct]/tolerance]} {
|
|
if {$badtime < [hvali [sct]/badreadingminutes] * 60 * 2} {
|
|
sct badtime [expr $badtime + $delta]
|
|
}
|
|
} else {
|
|
if {$badtime > $delta} {
|
|
sct badtime [expr $badtime - $delta]
|
|
} else {
|
|
sct badtime 0
|
|
}
|
|
}
|
|
hupdate [sct]/smooth $smooth
|
|
return $smooth
|
|
}
|
|
|
|
proc ccu::fillExt {type} {
|
|
set level [silent invalid result [hvali [sct]/readlevel]]
|
|
if {! [string is double $level]} {
|
|
hupdate [sct ${type}extpath]/status "illegal $type level reading"
|
|
return idle
|
|
}
|
|
set state [hvali [sct]]
|
|
set now [clock seconds]
|
|
set lowlevel [hvali [sct]/lowlevel]
|
|
if {$lowlevel < 0.0} {
|
|
hupdate [sct]/lowlevel 0.0
|
|
}
|
|
if {$lowlevel > 90.0} {
|
|
hupdate [sct]/lowlevel 90.0
|
|
}
|
|
set highlevel [hvali [sct]/highlevel]
|
|
if {$highlevel > 100.0} {
|
|
hupdate [sct]/highlevel 100.0
|
|
}
|
|
if {$highlevel < $lowlevel + 10.0} {
|
|
hupdate [sct]/highlevel [expr $lowlevel + 10.0]
|
|
}
|
|
switch -- $state {
|
|
-1 { # initializing
|
|
hupdate [sct]/status "automatic $type filling off"
|
|
hset [sct] 2
|
|
sct lasttime 0
|
|
return ${type}off
|
|
}
|
|
0 { # watching
|
|
set minspeed [expr - 100.0 / [hvali [sct]/minholdhours] / 3600.]
|
|
set maxspeed [expr - 100.0 / [hvali [sct]/maxholdhours] / 3600.]
|
|
set s [calcsmooth $level $minspeed $maxspeed]
|
|
if {$s < $lowlevel} {
|
|
if {[sct badtime] > [hvali [sct]/badreadingminutes] * 60} {
|
|
hupdate [sct]/status "automatic $type filling off - continuos bad reading"
|
|
hset [sct] 2
|
|
return ${type}off
|
|
}
|
|
# start fill
|
|
hset [sct] 1
|
|
clientput "$type level low - start fill"
|
|
}
|
|
return ${type}off
|
|
}
|
|
1 {
|
|
set vcmd [silent 0 sct vessel_cmd]
|
|
set now [clock seconds]
|
|
set s [hvali [sct]/smooth]
|
|
if {$s < [sct minlevel]} {
|
|
sct minlevel $s
|
|
}
|
|
set vessel 999
|
|
set maxspeed [expr 100.0 / [hvali [sct]/minfillminutes] / 60.]
|
|
if {[sct tubecooling] && $now < [sct startfill] + [hvali [sct]/tubecoolingminutes] * 60 && $s < [sct minlevel] + 5} {
|
|
# allow fill tube to get cold
|
|
set minspeed [expr - 100.0 / [hvali [sct]/maxfillminutes] / 60.]
|
|
} else {
|
|
if {[sct tubecooling]} {
|
|
sct tubecooling 0
|
|
if {$vcmd ne "0"} {
|
|
sct vstart_level [expr [result $vcmd]]
|
|
# start time - 2 minutes
|
|
sct vstart_time [expr $now - 120]
|
|
sct vlast_time $now
|
|
hupdate [sct]/vext [sct vstart_level]
|
|
}
|
|
}
|
|
set minspeed [expr + 100.0 / [hvali [sct]/maxfillminutes] / 60.]
|
|
if {$vcmd ne "0"} {
|
|
set dt [expr $now - [sct vlast_time]]
|
|
if {$dt > 0} {
|
|
set slope [expr ([hvali [sct]/vext] - [sct vstart_level])/([sct vlast_time] - [sct vstart_time])]
|
|
} else {
|
|
set slope 0
|
|
}
|
|
set vessel [result $vcmd]
|
|
set vext [expr [hvali [sct]/vext] + $slope * $dt]
|
|
if {$vext < $vessel} {
|
|
if {$vext < $vessel - 2.0} {
|
|
set errcnt [silent 0 sct errcnt]
|
|
incr errcnt
|
|
if {$errcnt > 3} {
|
|
hupdate [sct]/status "not enough progress on $type vessel - automatic ${type} filling off ($vext < $vessel - 2)"
|
|
hset [sct] 2
|
|
return ${type}off
|
|
}
|
|
sct errcnt $errcnt
|
|
} else {
|
|
set vessel $vext
|
|
}
|
|
}
|
|
hupdate [sct]/vext $vessel
|
|
sct vlast_time $now
|
|
}
|
|
}
|
|
if {$vessel < [silent 0 hvali [sct]/vessellimit]} {
|
|
hupdate [sct]/status "$type vessel empty - automatic ${type} filling off"
|
|
hset [sct] 2
|
|
return ${type}off
|
|
}
|
|
if {[calcsmooth $level $minspeed $maxspeed] <= $highlevel} {
|
|
# continue filling
|
|
return ${type}on
|
|
}
|
|
clientput "$type level high - stop fill"
|
|
hset [sct] 0
|
|
return ${type}off
|
|
}
|
|
2 {
|
|
set minspeed [expr - 100.0 / [hvali [sct]/minholdhours] / 3600.]
|
|
set maxspeed [expr + 100.0 / [hvali [sct]/minfillminutes] / 60.]
|
|
calcsmooth $level $minspeed $maxspeed
|
|
}
|
|
}
|
|
return idle
|
|
}
|
|
|
|
proc ccu::writeExt {type} {
|
|
sct update [sct target]
|
|
hupdate [sct]/smooth [silent 0 result [hvali [sct]/readlevel]]
|
|
switch -- [sct target] {
|
|
# watching
|
|
0 {
|
|
catch {logsetup [sct]/vext clear}
|
|
hupdate [sct]/status ""
|
|
eval [silent "expr 0" sct slow_cmd]
|
|
return ${type}off
|
|
}
|
|
# inactive
|
|
2 {
|
|
catch {logsetup [sct]/vext clear}
|
|
eval [silent "expr 0" sct slow_cmd]
|
|
if {[hvali [sct]/status] eq ""} {
|
|
hupdate [sct]/status "automatic ${type} filling off"
|
|
}
|
|
clientput "ERROR: [hvali [sct]/status]"
|
|
return ${type}off
|
|
}
|
|
# fill
|
|
1 {
|
|
hupdate [sct]/status ""
|
|
set vcmd [silent 0 sct vessel_cmd]
|
|
if {$vcmd ne "0"} {
|
|
set vlev [result $vcmd]
|
|
if {$vlev == 60} {
|
|
hupdate [sct]/status "vessel level meter not connected - automatic ${type} filling off"
|
|
clientput [hvali [sct]/status]
|
|
sct update 2
|
|
return ${type}off
|
|
}
|
|
if {$vlev < [hvali [sct]/vessellimit] + 3.0} {
|
|
hupdate [sct]/status "vessel not full enough - automatic ${type} filling off"
|
|
clientput [hvali [sct]/status]
|
|
sct update 2
|
|
return ${type}off
|
|
}
|
|
}
|
|
sct minlevel 100
|
|
sct errcnt 0
|
|
sct startfill [clock seconds]
|
|
sct tubecooling 1
|
|
eval [silent "expr 0" sct fast_cmd]
|
|
return ${type}on
|
|
}
|
|
}
|
|
return idle
|
|
}
|
|
|
|
proc ccu::writePrecool {} {
|
|
hupdate [sct]/status ""
|
|
sct update [sct target]
|
|
switch -- [sct target] {
|
|
# watching
|
|
0 {
|
|
return n2off
|
|
}
|
|
# inactive
|
|
2 {
|
|
hupdate [sct]/status "LN2 precooling off"
|
|
return n2off
|
|
}
|
|
1 {
|
|
return n2on
|
|
}
|
|
}
|
|
return idle
|
|
}
|
|
|
|
|
|
proc ccu::writeNv {} {
|
|
if {[sct target] != 5} {
|
|
eval [silent expr sct switchgraph] 1
|
|
}
|
|
hupdate [sct] [sct target]
|
|
switch -- [sct target] {
|
|
5 { # off
|
|
eval [silent expr sct switchgraph] 0
|
|
hupdate [sct]/speed 0
|
|
ccu::output nvOff
|
|
return stdSct::complete
|
|
}
|
|
0 { # fixed
|
|
hupdate [sct]/speed 0
|
|
ccu::output nvOff
|
|
return stdSct::complete
|
|
}
|
|
1 - 2 { # controlled or automatic:
|
|
sct beforefixed [sct target]
|
|
ccu::output nvOff
|
|
return stdSct::complete
|
|
}
|
|
3 { # close
|
|
if {[hvali [sct]/pos] < [hvali [sct]/pos/norm]} {
|
|
hupdate [sct]/pos [hvali [sct]/pos/norm]
|
|
}
|
|
}
|
|
4 { # open
|
|
if {[hvali [sct]/pos] > [hvali [sct]/pos/norm]} {
|
|
hupdate [sct]/pos [hvali [sct]/pos/norm]
|
|
}
|
|
}
|
|
}
|
|
return idle
|
|
}
|
|
|
|
proc ccu::writeSet {} {
|
|
switch -- [hvali [sct objectPath]] {
|
|
0 - 3 - 4 - 5 {
|
|
hset [sct objectPath] [sct beforefixed]
|
|
}
|
|
}
|
|
sct update [sct target]
|
|
return idle
|
|
}
|
|
|
|
proc ccu::adjustpos {delta} {
|
|
set pos [hvali [sct]/pos]
|
|
set normpos [hvali [sct]/pos/norm]
|
|
if {$pos < $normpos} {
|
|
if {$delta < 0} {
|
|
if {$pos <= 0} {
|
|
set pos 0
|
|
} else {
|
|
set pos [expr $pos + $delta * 0.2]
|
|
}
|
|
} else {
|
|
set pos [expr $pos + $delta]
|
|
}
|
|
} else {
|
|
if {$delta > 0} {
|
|
set pos [expr $pos + $delta * 0.5]
|
|
} else {
|
|
set pos [expr $pos + $delta]
|
|
}
|
|
}
|
|
if {$pos > [hvali [sct]/pos/max]} {
|
|
set pos [hvali [sct]/pos/max]
|
|
}
|
|
hupdate [sct]/pos $pos
|
|
}
|
|
|
|
proc ccu::trans args {
|
|
# transform all variables listed in the args
|
|
foreach var $args {
|
|
upvar $var val
|
|
if {$val > 4.0} {
|
|
set val [expr 3.3 + ($val - 4.0) * 0.1]
|
|
} elseif {$val > 3.5} {
|
|
set val [expr 3.2 + ($val - 3.5) * 0.2]
|
|
} elseif {$val > 3.0} {
|
|
set val [expr 3 + ($val - 3) * 0.4]
|
|
}
|
|
}
|
|
}
|
|
|
|
# polling script for nv control
|
|
proc ccu::control {} {
|
|
switch -- [hvali [sct]] {
|
|
0 - -1 - 5 { # fixed or off
|
|
if {[sct beforefixed] == 0} {
|
|
sct beforefixed 2
|
|
}
|
|
logsetup [sct]/set clear
|
|
set speed 0
|
|
return idle
|
|
}
|
|
3 { # closing
|
|
set soll -1
|
|
set speed -1000
|
|
}
|
|
4 { # opening
|
|
set soll -1
|
|
set speed 1000
|
|
}
|
|
1 { # controlled
|
|
set soll [hvali [sct]/set]
|
|
# the following line is for graphic (using flowtarget as nv_set curve)
|
|
catch {hupdate [sct]/autoflow/flowtarget $soll}
|
|
}
|
|
2 { # automatic
|
|
flow::task /nv/autoflow [hvali [sct]/set] [hvali [sct]/flowmax]
|
|
set soll [hvali [sct]/autoflow/flowset]
|
|
}
|
|
default {
|
|
return idle
|
|
}
|
|
}
|
|
|
|
if {$soll >= 0} {
|
|
# case 1 and 2: pid control
|
|
set ist [hvali [sct]/flow]
|
|
set tol [hvali [sct]/tolerance]
|
|
|
|
if {$soll > $ist + $tol} {
|
|
set soll [expr $soll - $tol]
|
|
} elseif {$soll < $ist - $tol} {
|
|
set soll [expr $soll + $tol]
|
|
} else {
|
|
set soll $ist
|
|
}
|
|
# transfer function taking into account heavy nonlinearities of needle valve
|
|
ccu::trans ist soll
|
|
|
|
set prop [hvali [sct]/prop]
|
|
set int [hvali [sct]/int]
|
|
set diff [expr $soll - $ist]
|
|
set lastdiff [silent $diff sct lastdiff]
|
|
sct lastdiff $diff
|
|
# carry is used for finetuning when speed is below 1
|
|
set carry [silent 0 sct carry]
|
|
set speed [expr $prop * ($diff - $lastdiff) + $int * $diff + $carry]
|
|
|
|
} else {
|
|
logsetup [sct]/set clear
|
|
}
|
|
set long 100
|
|
set short 10
|
|
set micro 1
|
|
set minpulse [hvali [sct]/minpulse]
|
|
if {$speed > 700} {
|
|
set speed 700
|
|
} elseif {$speed < -700} {
|
|
set speed -700
|
|
}
|
|
# clientput $speed
|
|
set normpos [hvali [sct]/pos/norm]
|
|
set maxpos [hvali [sct]/pos/max]
|
|
set pos [hvali [sct]/pos]
|
|
if {$speed < 0} {
|
|
set dir nvClose
|
|
set plen [expr -$speed]
|
|
set force normal
|
|
} else {
|
|
set dir nvOpen
|
|
set plen $speed
|
|
if {$pos < $normpos} {
|
|
set force strong
|
|
} else {
|
|
set force normal
|
|
}
|
|
}
|
|
sct pulser ccu::pulser
|
|
if {$plen >= $long * 0.9} {
|
|
set length long
|
|
sct utime endtime
|
|
set p [expr int($plen / $long)]
|
|
if {$p < 1} {
|
|
set p 1
|
|
}
|
|
sct pulsecnt $p
|
|
sct endtime [expr [sct endtime] + $plen * 0.001]
|
|
sct plen $plen
|
|
if {$plen == 700} {
|
|
# a pulse of more than 700 ms is extended to a 1000 ms hardware pulse
|
|
set plen 1000
|
|
sct pulser ccu::pulsend
|
|
} else {
|
|
sct pulser ccu::timer
|
|
}
|
|
} elseif {$plen >= $short * 0.9 && $minpulse <= 10} {
|
|
set length short
|
|
set p [expr int($plen / $short)]
|
|
if {$p > 5} {
|
|
set p 5
|
|
} elseif {$p < 1} {
|
|
set p 1
|
|
}
|
|
sct pulsecnt $p
|
|
set plen [expr $p * $short]
|
|
} elseif {$plen >= $micro * 0.9 && $minpulse <= 1} {
|
|
set length micro
|
|
set force strong
|
|
set p [expr int($plen / $micro)]
|
|
if {$p > 5} {
|
|
set p 5
|
|
} elseif {$p < 1} {
|
|
set p 1
|
|
}
|
|
sct pulsecnt $p
|
|
set plen [expr $p * $micro]
|
|
} else {
|
|
sct carry $speed
|
|
hupdate [sct]/speed 0
|
|
return idle
|
|
}
|
|
sct carry 0
|
|
if {$speed < 0} {
|
|
set speed -$plen
|
|
} else {
|
|
set speed $plen
|
|
}
|
|
hupdate [sct]/speed $speed
|
|
ccu::output $dir $force $length
|
|
ccu::adjustpos [expr $speed * 0.001]
|
|
return pulser
|
|
}
|
|
|
|
proc ccu::timer {} {
|
|
sct utime nowtime
|
|
if {[sct nowtime] > [sct endtime]} {
|
|
ccu::output nvOff
|
|
return stdSct::complete
|
|
}
|
|
ccu::output pulsOff
|
|
return ccu::timer
|
|
}
|
|
|
|
proc ccu::pulser {} {
|
|
set p [sct pulsecnt]
|
|
incr p -1
|
|
sct pulsecnt $p
|
|
if {$p <= 0} {
|
|
ccu::output nvOff
|
|
return stdSct::complete
|
|
}
|
|
ccu::output pulsOff
|
|
return ccu::pulser2
|
|
}
|
|
|
|
proc ccu::pulser2 {} {
|
|
ccu::output pulsOn
|
|
return ccu::pulser
|
|
}
|
|
|
|
proc ccu::ticker {} {
|
|
if {([sct dout] & 0x30) == 0} {
|
|
# do not tick when motor is off
|
|
return idle
|
|
}
|
|
ccu::output pulsOn
|
|
return ccu::pulsend
|
|
}
|
|
|
|
proc ccu::pulsend {} {
|
|
ccu::output pulsOff
|
|
return stdSct::complete
|
|
}
|
|
|
|
proc ccu::unpoll {} {
|
|
return unpoll
|
|
}
|