616 lines
16 KiB
Tcl
616 lines
16 KiB
Tcl
# humidity cell virtual driver
|
|
#
|
|
|
|
namespace eval hcell {} {
|
|
}
|
|
|
|
proc stdConfig::hcell {{label "Climate Chamber Main"}} {
|
|
variable hostport none
|
|
controller syncedprot 30
|
|
prop write stdSct::complete
|
|
|
|
pollperiod 1 1
|
|
|
|
obj sans2_humidity_cell -drive out
|
|
prop status idle
|
|
prop checklimits hcell::checklimits
|
|
prop runlabel actual
|
|
prop write hcell::write
|
|
prop halt hcell::halt
|
|
prop label "rel. humidity set"
|
|
|
|
kids $label {
|
|
node gas_bottle_open -int out
|
|
prop label "gas bottle (check after manual valves opened, uncheck after manual valves closed)"
|
|
default 0
|
|
prop enum 1
|
|
prop check hcell::gas_bottle
|
|
|
|
node state -int rd
|
|
prop enum init,open_gas_bottle,check_pressure,initialize,start,go_to_basic_state,stable,changing,shut_down,shutting_down_flush,shutting_down_cool,close_gas_bottle,power_off
|
|
prop read hcell::poll
|
|
prop lasttime 0
|
|
prop rh_start -1
|
|
prop intolerance 0
|
|
|
|
node rh_set upd
|
|
|
|
node rh_regulation out
|
|
prop enum 1
|
|
default 1
|
|
prop write hcell::write_rh_regulation
|
|
|
|
node tolerance par 1
|
|
|
|
node settle par 120
|
|
|
|
node maxwait par 1800
|
|
|
|
node rh_corr par 0
|
|
|
|
node rh_corr_time par 60
|
|
|
|
node rh_corr_max par 4
|
|
|
|
node rh_calc upd
|
|
|
|
node rh_delta upd
|
|
|
|
node x upd
|
|
|
|
node psat upd
|
|
|
|
node active par 1
|
|
prop enum 1
|
|
|
|
node sample_change -int out
|
|
default 0
|
|
prop enum 1
|
|
prop check hcell::check_complete
|
|
prop label "check for a sample change, uncheck when finished"
|
|
prop groupMode expert
|
|
|
|
kids "sample change settings" {
|
|
node state -int upd
|
|
default 0
|
|
prop enum idle,waiting,allowed
|
|
|
|
node t_sample par 35
|
|
|
|
node t_lim par 40
|
|
|
|
node t_tube par 40
|
|
|
|
node t_cem par 50
|
|
|
|
node fg_ini par 100
|
|
|
|
node rh_ini par 20
|
|
|
|
}
|
|
|
|
node cfg -none
|
|
prop groupMode expert
|
|
|
|
kids "operation settings" {
|
|
node t_min par 50
|
|
|
|
node t_max par 100
|
|
|
|
node rh_min par 20
|
|
|
|
node rh_max par 95
|
|
|
|
node press par 1070.0
|
|
|
|
node press_dif_cem par 1500.0
|
|
|
|
node mgas par 28.013
|
|
prop newline 1
|
|
|
|
node mliq par 18.015
|
|
|
|
node fw_min par 10
|
|
prop newline 1
|
|
|
|
node fg_min par 15.5
|
|
prop newline 1
|
|
|
|
node rh_max_delta_rel par 1
|
|
|
|
node rh_max_cem par 50.0
|
|
|
|
node rh_max_tube par 80.0
|
|
|
|
}
|
|
|
|
node dev -none
|
|
prop groupMode expert
|
|
|
|
kids "device settings" {
|
|
node fw_max par 100
|
|
|
|
node fw_delta_abs par 0.1
|
|
|
|
node fg_max par 775.8
|
|
|
|
node fg_delta_abs par 0.72
|
|
|
|
node fg_delta_percent par 0.5
|
|
|
|
}
|
|
|
|
node ini -none
|
|
prop groupMode expert
|
|
|
|
kids "initialisation settings" {
|
|
node t_cem par 0
|
|
|
|
node t_tube par 20
|
|
|
|
node t_sample par 0
|
|
|
|
node fg_ini par 100
|
|
|
|
node rh_ini par 20
|
|
|
|
}
|
|
|
|
node shut_down -int out
|
|
prop label "check for shutting down, uncheck to init again (do not power off before 'power off' message)"
|
|
prop enum 1
|
|
prop check hcell::check_complete
|
|
|
|
}
|
|
}
|
|
|
|
proc hcell::checklimits {} {
|
|
get_values [sct]/cfg rh_max
|
|
if {[sct target] > $rh_max} {
|
|
error "[sct] [sct target] is above limit"
|
|
}
|
|
}
|
|
|
|
proc hcell::write {} {
|
|
get_values [sct]/cfg rh_max
|
|
if {[sct target] > $rh_max} {
|
|
error "[sct] [sct target] is above limit"
|
|
}
|
|
hupdate [sct]/rh_set [sct target]
|
|
sct status run
|
|
return idle
|
|
}
|
|
|
|
proc hcell::write_regulation {} {
|
|
sct update [sct target]
|
|
if {[sct target] == 0} {
|
|
hupdate [sct objectPath]/rh_corr 0
|
|
}
|
|
return idle
|
|
}
|
|
|
|
proc hcell::halt {} {
|
|
set t [hvali /t]
|
|
hset /t [hvali /t]
|
|
sct print "halt at T=$t rh=[hvali [sct]/rh_set]"
|
|
sct status posfault
|
|
}
|
|
|
|
proc hcell::psat {temp_c} {
|
|
# saturation pressure of water (mbar)
|
|
return [expr 10 ** (8.196 - 1730 / (233.4 + $temp_c))]
|
|
}
|
|
|
|
proc hcell::calc_t {rh fg fw {for_cem 0}} {
|
|
# temperature for given flows and rel. humidity
|
|
# get_values [sct objectPath]/cfg mliq mgas press
|
|
get_values /rh/cfg mliq mgas press press_dif_cem
|
|
if {$for_cem} {
|
|
set press [expr $press + $press_dif_cem]
|
|
}
|
|
set psat [expr $press * 100 / $rh * $fw / ($fg * $mliq / $mgas + $fw)]
|
|
set temp [expr 1730/(8.196 - log10($psat)) - 233.4]
|
|
return $temp
|
|
}
|
|
|
|
proc hcell::calc_flows {temp_c rh} {
|
|
|
|
get_values [sct objectPath]/cfg press mliq mgas fw_min fg_min rh_max rh_max_delta_rel
|
|
get_values [sct objectPath]/dev fw_max fg_max fg_delta_abs fg_delta_percent fw_delta_abs
|
|
get_values [sct objectPath] rh_corr
|
|
|
|
set rh [expr $rh + $rh_corr]
|
|
|
|
set rh_max_delta_rel [expr max($rh_max_delta_rel, $fg_delta_percent, $fg_delta_abs / $fw_max * $mliq / $mgas)]
|
|
putIntoLimits rh_max_delta_rel $fg_delta_percent 100
|
|
putIntoLimits rh 0.001 $rh_max
|
|
|
|
set psat [hcell::psat $temp_c]
|
|
set x [expr $mliq / $mgas / max(1e-6, $press * 100.0 / $rh / $psat - 1.0)]
|
|
|
|
set fwg [expr $fg_delta_abs * 100.0 \
|
|
/ (($rh_max_delta_rel - $fg_delta_percent) / $x + $rh_max_delta_rel * $mgas / $mliq)]
|
|
set fww [expr 100 * $fw_delta_abs / $rh_max_delta_rel]
|
|
set fw [expr min($fw_max, max($fw_min, sqrt($fww ** 2 + $fwg ** 2)))]
|
|
set fg [expr min($fg_max, max($fg_min, $fw / $x))]
|
|
#clientput "$fwg $fww $fw $fg"
|
|
set fw [expr min($fw_max, $fg * $x)]
|
|
set fg [expr $fw / $x]
|
|
|
|
set rh0 [hcell::calc_rh $fg $fw $temp_c]
|
|
set rh1 [hcell::calc_rh [expr $fg * (1 - $fg_delta_percent * 0.01) - $fg_delta_abs] $fw $temp_c]
|
|
set rh2 [hcell::calc_rh $fg [expr $fw - $fw_delta_abs] $temp_c]
|
|
#clientput [format "%6.2f %6.2f %6.2f %6.2f" $fg $fw [expr abs($rh1 - $rh) * 100.0 / $rh] [expr abs($rh2 - $rh) * 100.0 / $rh]]
|
|
return [list $fg $fw $x [expr sqrt(abs(($rh1 - $rh) ** 2 + abs($rh - $rh2) ** 2)) * 100.0 / $rh]]
|
|
}
|
|
|
|
proc hcell::calc_rh {fg fw temp_c} {
|
|
get_values [sct objectPath]/cfg press mgas mliq
|
|
set psat [hcell::psat $temp_c]
|
|
set pliq [expr $press * $fw / ($fw + $fg * $mliq / $mgas)]
|
|
return [expr 100.0 * $pliq / $psat]
|
|
}
|
|
|
|
proc hcell::modify {path setpoint {tolerance 0} {ramppath ""} {ramp 10}} {
|
|
set old [hvali $path]
|
|
if {abs($old - $setpoint) > $tolerance} {
|
|
if {$ramppath ne ""} {
|
|
hset $ramppath $ramp
|
|
}
|
|
hset $path $setpoint
|
|
}
|
|
}
|
|
|
|
proc hcell::specials {nextaction} {
|
|
if {[hvali [sct objectPath]/shut_down]} {
|
|
enum_update [sct objectPath]/state shut_down
|
|
return 1
|
|
}
|
|
set sch [hvali [sct objectPath]/sample_change]
|
|
get_values [sct objectPath]/sample_change t_tube t_cem t_sample t_lim
|
|
set state [enum_txt [sct objectPath]/sample_change/state]
|
|
if {$sch} {
|
|
switch $state {
|
|
waiting {
|
|
get_values / t
|
|
if {$t <= $t_lim} {
|
|
updateval [sct objectPath]/status "sample change: it is safe to open the chamber"
|
|
enum_update [sct objectPath]/sample_change/state allowed
|
|
fg set 0
|
|
fw set 0
|
|
}
|
|
return 1
|
|
}
|
|
allowed {
|
|
return 1
|
|
}
|
|
}
|
|
# state idle
|
|
t set $t_sample
|
|
t_tube set $t_tube
|
|
t_cem set $t_cem
|
|
fw set 0
|
|
fg set 18
|
|
|
|
enum_update [sct objectPath]/sample_change/state waiting
|
|
updateval [sct objectPath]/status "sample change: waiting for T < $t_tube C"
|
|
return 1
|
|
} else {
|
|
if {$state eq "idle"} {
|
|
return 0
|
|
}
|
|
enum_update [sct objectPath]/sample_change/state idle
|
|
updateval [sct objectPath]/status ""
|
|
enum_update $nextaction
|
|
return [expr {[enum_txt [sct objectPath]/state] ne $nextaction}]
|
|
}
|
|
}
|
|
|
|
proc hcell::modify_x {tf rh_set now} {
|
|
set rh_old [calc_rh [sctval /fg/set] [sctval /fw/set] $tf]
|
|
lassign [calc_flows $tf $rh_set] fg fw x rh_delta
|
|
set rh_new [calc_rh $fg $fw $tf]
|
|
set dif [expr abs($rh_new - $rh_old) / max(10,$rh_new)]
|
|
if {$dif > 0.001} {
|
|
if {$dif > 0.01 || $dif > 0.01*exp(([silent 0 sct last_x_time] - $now)/130.0)} {
|
|
sct last_x_time $now
|
|
hset /fg/set $fg
|
|
hset /fw/set $fw
|
|
#clientput "modify_x tf $tf rh $rh_set rh_new $rh_new rh_old $rh_old fg $fg fw $fw"
|
|
update_values [sct objectPath] rh_delta x
|
|
}
|
|
}
|
|
}
|
|
|
|
proc hcell::poll {} {
|
|
if {![hval [sct objectPath]/active]} {
|
|
return idle
|
|
}
|
|
set now [doubleTime]
|
|
set phase [enum_txt [sct]]
|
|
|
|
switch -- $phase {
|
|
shutting_down - end { }
|
|
init {
|
|
clientput INIT
|
|
get_values [sct objectPath]/ini t_cem t_sample rh_ini
|
|
hset /t_cem/set $t_cem
|
|
hset /t_tube/set $t_cem
|
|
t_cem0 ctrlmode 4
|
|
hset /t/set $t_sample
|
|
hset /fw/ramp 99999
|
|
hset /fw/set 0
|
|
hset /fg/ramp 99999
|
|
hset /fg/set 0
|
|
hset [sct objectPath]/rh_set $rh_ini
|
|
hsetprop /rh target $rh_ini
|
|
updateval [sct objectPath]/status "please open gas bottle and confirm"
|
|
enum_update open_gas_bottle
|
|
return idle
|
|
}
|
|
open_gas_bottle {
|
|
if {[hcell::specials init]} {
|
|
return idle
|
|
}
|
|
if {[hvali [sct objectPath]/gas_bottle_open]} {
|
|
enum_update check_pressure
|
|
}
|
|
return idle
|
|
}
|
|
check_pressure {
|
|
clientput CHECK_PRESSURE
|
|
updateval [sct objectPath]/status ""
|
|
enum_update initialize
|
|
get_values [sct objectPath]/ini fg_ini
|
|
hset /fg/set $fg_ini
|
|
get_values [sct objectPath]/cfg press t_min
|
|
hset /t_cem/set $t_min
|
|
hset /t_tube/set $t_min
|
|
hupdate /t/reg $t_min
|
|
#hupdate /ps/set $press
|
|
#hupdate /ps/reg $press
|
|
updateval [sct objectPath]/status "waiting for pressure"
|
|
sct phasestart $now
|
|
}
|
|
initialize {
|
|
hcell::specials check_pressure
|
|
get_values [sct objectPath]/cfg press
|
|
get_values [sct objectPath]/ini fg_ini
|
|
get_values / ps ps/calib_max
|
|
set fg_set [hvali /fg/set]
|
|
if {$ps >= $ps_calib_max - 1} {
|
|
if {$fg_set > 0} {
|
|
clientput "switch off gas, pressure too high"
|
|
hset /fg/ramp 99999
|
|
hset /fg/set 0
|
|
}
|
|
sct phasestart $now
|
|
} elseif {$ps < ($press + $ps_calib_max) * 0.5} {
|
|
if {$fg_set == 0} {
|
|
clientput "switch gas on again"
|
|
hset /fg/ramp 99999
|
|
hset /fg/set $fg_ini
|
|
}
|
|
}
|
|
if {$fg_set != 0 && $now > [sct phasestart] + 60} {
|
|
enum_update start
|
|
}
|
|
}
|
|
start {
|
|
clientput START
|
|
updateval [sct objectPath]/status ""
|
|
enum_update go_to_basic_state
|
|
get_values [sct objectPath]/cfg t_min rh_max_cem rh_max_tube rh_min
|
|
get_values [sct objectPath]/ini fg_ini rh_ini
|
|
hset /fw/set 0
|
|
hset /fg/set $fg_ini
|
|
hset /t_cem/set $t_min
|
|
hset /t_tube/set $t_min
|
|
hupdate /t/reg $t_min
|
|
hset /t/set $t_min
|
|
hset [sct objectPath]/rh_set $rh_ini
|
|
sct phasestart $now
|
|
}
|
|
go_to_basic_state {
|
|
if {[hcell::specials start]} {
|
|
return idle
|
|
}
|
|
get_values [sct objectPath]/cfg t_min
|
|
if {$now > [sct phasestart] + 20 && [hvali /t] > $t_min - 1} {
|
|
clientput STABLE
|
|
updateval [sct objectPath]/status ""
|
|
enum_update stable
|
|
sct phasestart $now
|
|
}
|
|
}
|
|
stable - changing {
|
|
if {[hcell::specials start]} {
|
|
return idle
|
|
}
|
|
get_values / t t/set t/reg t/ramp fg fw fg/set fw/set fg/ramp fw/ramp fg/reg fw/reg rhs
|
|
get_values [sct objectPath] rh_set rh_corr_time tolerance settle maxwait
|
|
get_values [sct objectPath]/cfg press mliq mgas rh_max_cem rh_max_tube
|
|
get_values [sct objectPath]/dev fg_delta_abs fw_delta_abs fg_delta_percent
|
|
|
|
# set tf to the temperature relevant for the calculation
|
|
if {$t < $t_reg} {
|
|
set tf [expr min($t_reg, $t + 0.1)]
|
|
} else {
|
|
set tf [expr max($t_reg, $t * 0.8)]
|
|
}
|
|
|
|
modify_x $tf $rh_set $now
|
|
|
|
if {[silent 0 sct rh_set] != $rh_set || [silent 0 sct t_set] != $t_set} {
|
|
clientput "changing to $rh_set $t_set"
|
|
enum_update changing
|
|
run t $t_set
|
|
sct phasestart $now
|
|
sct rh_set $rh_set
|
|
sct t_set $t_set
|
|
sct settle 0
|
|
sct rh_settled 0
|
|
sct intolerance 0
|
|
}
|
|
if {abs($rh_set - $rhs) < $tolerance} {
|
|
if {! [sct intolerance]} {
|
|
sct rh_start [expr $now - [sct rh_settled]]
|
|
sct intolerance 1
|
|
}
|
|
} else {
|
|
if {[sct intolerance]} {
|
|
sct rh_settled [expr $now - [sct rh_start]]
|
|
}
|
|
}
|
|
if {[hgetpropval /t status] eq "idle" &&
|
|
abs($fg_set - $fg_set) < 0.2 &&
|
|
abs($fw_set - $fw_set) < 0.2} {
|
|
if {$phase ne "stable" && $now > [sct phasestart] + 15} {
|
|
enum_update stable
|
|
sct phasestart $now
|
|
}
|
|
}
|
|
if {$phase eq "stable"} {
|
|
if {$now < [sct phasestart] + $rh_corr_time} {
|
|
hcell::update_calc_rh
|
|
} else {
|
|
hcell::update_calc_rh $rhs
|
|
}
|
|
if {[sct intolerance] && $now - [sct rh_start] > $settle} {
|
|
hsetprop [sct objectPath] status idle
|
|
}
|
|
} else {
|
|
hcell::update_calc_rh
|
|
}
|
|
|
|
set t_cem [hcell::calc_t $rh_max_cem $fg_reg $fw_reg 1]
|
|
set t_tube [hcell::calc_t $rh_max_tube $fg_reg $fw_reg]
|
|
|
|
hcell::modify /t_cem/set [expr max($t_set, $t_cem)] 1
|
|
hcell::modify /t_tube/set [expr max($t_set, $t_tube)] 1
|
|
|
|
# set psat [hcell::psat $t]
|
|
# update_values [sct objectPath] psat
|
|
}
|
|
shut_down {
|
|
get_values [sct objectPath]/cfg t_min rh_max_cem rh_max_tube rh_min
|
|
get_values [sct objectPath]/ini fg_ini rh_ini
|
|
get_values [sct objectPath]/dev fg_max
|
|
hset /fw/ramp 99999
|
|
hset /fw/set 0
|
|
hset /fg/ramp 99999
|
|
hset /fg/set $fg_max
|
|
hset /t_cem/set 0
|
|
hset /t_tube/set $t_min
|
|
hset /t/set $t_min
|
|
sct phasestart $now
|
|
enum_update shutting_down_flush
|
|
updateval [sct objectPath]/status "flushing before shut down"
|
|
}
|
|
shutting_down_flush {
|
|
if {![hval [sct objectPath]/shut_down]} {
|
|
enum_update start
|
|
return idle
|
|
}
|
|
if {$now > [sct phasestart] + 60} {
|
|
get_values [sct objectPath]/ini t_cem t_sample
|
|
hset /t_cem/set $t_cem
|
|
hset /t_tube/set $t_cem
|
|
hset /t/set $t_sample
|
|
sct phasestart $now
|
|
enum_update shutting_down_cool
|
|
updateval [sct objectPath]/status "cool down before shut down"
|
|
}
|
|
}
|
|
shutting_down_cool {
|
|
if {![hval [sct objectPath]/shut_down]} {
|
|
enum_update start
|
|
return idle
|
|
}
|
|
if {$now > [sct phasestart] + 180} {;# should be 180
|
|
enum_update close_gas_bottle
|
|
updateval [sct objectPath]/status "flush and empty the system manually, close gas bottle and confirm"
|
|
}
|
|
}
|
|
close_gas_bottle {
|
|
hset /fw/ramp 99999
|
|
hset /fw/set 0
|
|
hset /fg/ramp 99999
|
|
hset /fg/set 0
|
|
if {![hvali [sct objectPath]/gas_bottle_open]} {
|
|
updateval [sct objectPath]/status "it is now safe to switch off power"
|
|
enum_update power_off
|
|
return idle
|
|
}
|
|
if {![hval [sct objectPath]/shut_down]} {
|
|
enum_update start
|
|
return idle
|
|
}
|
|
}
|
|
power_off {
|
|
if {![hval [sct objectPath]/shut_down]} {
|
|
enum_update init
|
|
return idle
|
|
}
|
|
}
|
|
}
|
|
hcell::update_calc_rh
|
|
return idle
|
|
}
|
|
|
|
proc hcell::update_calc_rh {{rh_sensor none}} {
|
|
set opa [sct objectPath]
|
|
get_values "" fg fw t
|
|
get_values $opa rh_set rh_corr rh_corr_time rh_corr_max rh_regulation
|
|
if {$fg > 0 && $fw > 0} {
|
|
set rh [hcell::calc_rh $fg $fw $t]
|
|
set rh_calc [expr min(120., $rh)]
|
|
} elseif {$fg == 0} {
|
|
set rh_calc 120.
|
|
} else {
|
|
set rh_calc 0
|
|
}
|
|
hupdate $opa/rh_calc $rh_calc
|
|
set now [DoubleTime]
|
|
set deltat [expr min(10.0,$now - [sct lasttime])]
|
|
sct lasttime $now
|
|
|
|
set psat [hcell::psat $t]
|
|
update_values $opa rh_set psat
|
|
sct update [hvali [sct]]
|
|
if {!$rh_regulation} {
|
|
hupdate $opa $rh_calc
|
|
return 0
|
|
}
|
|
if {$rh_sensor eq "none"} {
|
|
hupdate $opa [silent $rh_calc hval /rhs]
|
|
return 0
|
|
}
|
|
hupdate $opa $rh_sensor
|
|
set w [expr $deltat / $rh_corr_time]
|
|
set rh_corr [expr $rh_corr * (1 - $w) + $w * ($rh_calc - $rh_sensor)]
|
|
putIntoLimits rh_corr -$rh_corr_max $rh_corr_max
|
|
hupdate $opa/rh_corr $rh_corr
|
|
return 1
|
|
}
|
|
|
|
proc hcell::gas_bottle {} {
|
|
if {![sct target] && [enum_txt [sct objectPath]/state] ne "close_gas_bottle"} {
|
|
hupdate [sct objectPath]/status "please close gas bottle only before shutdown"
|
|
} else {
|
|
hupdate [sct objectPath]/status ""
|
|
}
|
|
sct update [sct target]
|
|
[sct controller] queue [sct objectPath]/state progress hcell::poll
|
|
}
|
|
|
|
proc hcell::check_complete {} {
|
|
sct update [sct target]
|
|
[sct controller] queue [sct objectPath]/state progress hcell::poll
|
|
}
|
|
|
|
proc hcell::write_rh_regulation {} {
|
|
sct update [sct target]
|
|
return idle
|
|
}
|