Files
sea/tcl/drivers/ccu4ext.tcl
Markus Zolliker 893bd5fa4b automatic LN2 fill with ILM - fix problems on restart
on the first reading, the level value might be invalid,
then it tries to fill and switch to inactive because there
is not valid level value.

fix: stay watching as long as the level is invalid
2025-07-04 10:51:54 +02:00

424 lines
11 KiB
Tcl

namespace eval ccu4ext {
}
# automatic fill with level reading from external source
proc stdConfig::ccu4ext {type {readpath 0}} {
variable node
variable ctrl
controller syncedprot
obj fillccu wr -int
default -1
prop write ccu4ext::writeExt $type
prop read ccu4ext::fillExt $type
prop enum watching=0,filling=1,inactive=2,manualfill=3
prop label "filling state"
if {$type eq "n2"} {
set text LN2
} else {
set text LHe
}
kids "$text fill settings" {
node state upd -text
node readpath -text par $readpath
prop visible false
node lowlevel par 10
node highlevel par 100
node smooth upd
prop fmt %.7g
prop geterror undefined
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
return "$text fill with CCU4 and external level meter"
}
proc ccu4ext::changestate {type state} {
switch $type {
n2 {
set cmd nc
set auto na
}
he {
set cmd hcd
set auto ha
}
default {
error "illegal level meter type: $type"
}
}
switch $state {
stop {
cc $cmd 0
cc $auto 1
}
fill {
cc $cmd 1
cc $auto 1
}
off {
cc $cmd 2
cc $auto 0
}
watch {
cc $cmd 3
cc $auto 1
}
}
sct change_time [DoubleTime]
}
proc ccu4ext::calcsmooth {level minspeed maxspeed} {
if {![string is double $level]} {
return 199
}
set now [clock seconds]
set lasttime [silent 0 sct lasttime]
if {$lasttime == 0} {
set lasttime $now
hupdate [sct]/smooth $level
}
set delta [expr $now - $lasttime]
sct lasttime $now
set smooth [silent $level hvali [sct]/smooth]
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
hdelprop [sct]/smooth geterror
return $smooth
}
proc ccu4ext::setmode {type state} {
if {[sctval [sct]] != $state} {
sct target $state
writeExt $type 0
}
}
proc ccu4ext::fillExt {type} {
if {$type eq "n2"} {
set valve [sctval /cc/nv]
set auto [sctval /cc/na]
} else {
set valve [sctval /cc/hv]
set auto [sctval /cc/ha]
if {[sctval /cc/hav] != 2} { # must be on 2=ext
hset /cc/hav 2
}
}
set errtxt ""
set now [DoubleTime]
set sm [expr $now > [silent 0 sct change_time] + 10]
switch -- $valve {
0 {
set txt "fill valve off"
if {$sm} {
if {$auto} {
setmode $type 0
} else {
setmode $type 2
}
}
}
1 {
set txt "filling"
if {$sm} {setmode $type 1}
}
2 {
set txt "no fill valve"
if {$sm} {setmode $type 2}
}
3 {
set errtxt timeout
}
4 {
set errtxt timeout1
}
default {
set errtxt "unknown error"
}
}
set rdpath [hvali [sct]/readpath]
set level [hvali $rdpath]
set levelerror [silent "" hgetpropval $rdpath geterror]
if {$levelerror ne "" && $errtxt eq "" && [hvali [sct]] < 2} {
set errtxt "$txt ($rdpath $levelerror)"
}
if {$errtxt eq ""} {
updateval [sct]/state $txt
hupdate [sct]/status ""
} else {
hupdate [sct]/status $errtxt
updateval [sct]/state $errtxt
}
if {[sctval [sct]] == 3} { # manualfill
changestate $type fill
return idle
}
set state [sctval [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] 0
sct lasttime 0
changestate $type watch
return idle
}
0 { # watching
if {[validated_level] eq ""} {
clientput "invalid $type level - suspend watching"
return idle
}
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
changestate $type off
return idle
}
# start fill
hset [sct] 1
clientput "$type level low ($level smooth $s) - start fill"
} else {
changestate $type stop
}
return idle
}
1 { # filling
if {[validated_level] eq ""} {
hset [sct] 2
changestate $type off
return idle
}
set vcmd [silent 0 sct vessel_cmd]
# check that vessel command works
if {$vcmd ne "0" && [catch {[result $vcmd]}]} {
clientput "WARNING: He vessel reading is configured, but not connected"
sct vessel_cmd 0
set vmd 0
}
set now [clock seconds]
set s [silent $level hval [sct]/smooth]
sct minlevel [silent 999 sct minlevel]
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
changestate $type off
return idle
}
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
changestate $type off
return idle
}
if {[calcsmooth $level $minspeed $maxspeed] <= $highlevel} {
# continue filling
changestate $type fill
return idle
}
clientput "$type level high - stop fill"
hset [sct] 0
changestate $type watch
return idle
}
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 ccu4ext::validated_level {} {
set rdpath [hval [sct]/readpath]
if {[DoubleTime] < [silent 0 hgetpropval $rdpath read_time] + 30} {
return [hval $rdpath]
}
if {[silent "" hgetpropval $rdpath geterror] eq ""} {
clientlog "ERROR: no reading of $rdpath within 30 sec"
}
hsetprop $rdpath geterror not_available
return ""
}
proc ccu4ext::writeExt {type {activate 1}} {
sct update [sct target]
switch -- [sct target] {
# watching
0 {
set level [validated_level]
if {$level eq ""} {
changestate $type off
sct update 2
return idle
}
hupdate [sct]/smooth $level
catch {logsetup [sct]/vext clear}
hupdate [sct]/status ""
eval [silent "expr 0" sct slow_cmd]
if {$activate} {
changestate $type watch
}
return idle
}
# 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]"
if {$activate} {
changestate $type off
}
return idle
}
# fill
1 {
set level [validated_level]
if {$level eq ""} {
changestate $type off
sct update 2
return idle
}
hupdate [sct]/smooth $level
hupdate [sct]/status ""
set vcmd [silent 0 sct vessel_cmd]
if {$vcmd ne "0"} {
set vlev [silent 60 result $vcmd]
if {$vlev == 60} {
clientput "WARNING: vessel level meter not connected - proceed without it"
sct vessel_cmd 0
} elseif {$vlev < [hvali [sct]/vessellimit] + 3.0} {
hupdate [sct]/status "vessel not full enough - automatic ${type} filling off"
clientput [hvali [sct]/status]
sct update 2
if {$activate} {
changestate $type off
}
return idle
}
}
sct minlevel 100
sct errcnt 0
sct startfill [clock seconds]
sct tubecooling 1
# eval [split [silent "expr 0" sct fast_cmd]]
eval [silent "expr 0" sct fast_cmd]
if {$activate} {
changestate $type fill
}
return idle
}
# manualfill
3 {
changestate $type fill
}
}
return idle
}