Files
sea/tcl/drivers/hcell.tcl
2022-08-18 15:04:28 +02:00

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
}