# 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 }