# calibration scripts # # in config file (.stick) # # makenv calib -driver zcalib # # see also: http://linse-c.psi.ch:8080/sample_environment/83 namespace eval zcalib { variable buf } proc stdConfig::zcalib {} { controller syncedprot 30 prop write stdSct::complete obj ZCALIB out -int default 3 prop check zcalib::command prop label "calibration" prop enum running,pause,continue,stop prop buttons start/0/0-2:pause/1/1-3:continue/2/0,2:stop/3/3 kids calib { node tlist out -text default "1.38 logstep0.04 310" prop check zcalib::set_tlist prop label "T list" prop help "a list of temperatures, logstep0.04 creates 25 logarithmic steps per decade in between values" prop width 80 node tindex out -int default 0 prop label "T index" prop write zcalib::set_tindex node n_temp upd -int prop label "number of T's" node mode out -int default 0 prop enum waiting,measuring prop write zcalib::set_mode node phase out -int default 0 prop write zcalib::set_phase node scanpos out -int default 0 prop write zcalib::set_scanpos prop help "index within one measuring cycle: 0: reference, 1: 1st chan, 2: ref, 3: 2nd chan, ..." node channels par -text "3 1 2 4 5 6" prop help "first channel is calibrated one" prop width 20 node basename par -text "/home/l_samenv/sea/calib_scripts/calib_data/calib%s_p%d_c%d.dat" prop width 40 node startdate par -text "" node tolerance par -float 0.005 prop help "do not measure when (T_Vti - T_set) > tolerance * T_set" node slopelimit par -float 0.005 prop help "do not measure before maxslope < slopelimit" node maxslope upd prop help "max of dR/R/window over all channels" node window par -float 600 prop help "window for slope calculation" node nmean par -int 10 } } proc zcalib::get_temp {index} { set T 0 set logN 0 set mode single set tlist [sctval /calib/tlist] foreach item $tlist { if {[string match logstep* $item]} { if {$T == 0} { error "logstep must not appear at the beginning" } set logstep 0 scan $item logstep%f logstep set mode logstep } else { if {$mode eq "logstep"} { if {$logstep == 0} { set logstep $item continue } set n [expr abs(round((log10($item) - log10($T)) / $logstep))] if {$index < $n - 1} { if {$item > $T} { set i [expr 1+$index] } else { set i [expr -1-$index] } set dig [expr round(2 - log10($logstep))] return [format %.${dig}g [expr $T * pow(10, $i * $logstep)]] } else { set mode single if {$n != 0} { incr index [expr 1-$n] } } } if {$index == 0} { return $item } set T $item incr index -1 } } return -$index } proc zcalib::clear_buf {} { variable buf foreach c [hval /calib/channels] { set buf($c) [list] set buf(dif$c) 1 } } proc zcalib::set_tlist {} { set n [get_temp 1000] incr n 1000 updateval [sct parent]/n_temp $n sct update [sct target] } proc zcalib::activate_nv {} { if {[result tt tm] < 12 && [result tt set] < 3 && [result nv] == 0} { # do not switch control on when already fixed at low T } elseif {[result tt set] > [result tt tm] - 10} { nv 1 } else { nv 2 } } proc zcalib::set_tindex {} { set i [sct target] set T [get_temp $i] if {$T <= 0} { clientput "close valve and stop pump" hepump valve 1 hepump running 0 calib 3 return idle } clear_buf sct update $i clientput "next_temp $T" tt set $T res autoscan 1 activate_nv return idle } proc zcalib::set_mode {} { updateerror /calib/maxslope undefined if {[sct target] == 0} { res autoscan 1 activate_nv } else { res autoscan 0 nv 0 } clear_buf sct update [sct target] return idle } proc zcalib::set_phase {} { sct update [sct target] hset [sct parent]/scanpos 0 return idle } proc zcalib::set_scanpos {} { sct update [sct target] if {[sct target] % 2 != 0} { set i [expr [sct target] / 2 + 1] set c [lindex [hval /calib/channels] $i] } else { set c [lindex [hval /calib/channels] 0] } res s$c/active 1 return idle } proc zcalib::update_chan {value} { variable buf catch { if {[hval /calib] == 1} { # how to treat a real pause? return } set time [format %.3f [expr [DoubleTime] - [hgetpropval /calib @basetime]]] set c [sct @channel] if {$c != [hval /res]} { return } set channels [hval /calib/channels] if {[hval /calib/mode] == 0} { lappend buf($c) $time lappend buf($c) $value set i 0 set vmin 1e30 set vmax 0 set idx 0 set full 0 # determine average set sumtime 0 set sumres 0 set n 0 set w [hval /calib/window] foreach {t v} $buf($c) { if {$t < $time - $w} { set idx $i } else { set sumtime [expr $sumtime + $t] set sumres [expr $sumres + $v] incr n } incr i 2 } if {$idx > 0} { # cut down buf to window size set buf($c) [lrange $buf($c) $idx end] } if {$n < 2} { set dt 0 } else { set meantime [expr $sumtime / double($n)] set meanres [expr $sumres / double($n)] # determine slope set sumdt 0 set sumdr 0 foreach {t v} $buf($c) { set dt [expr ($t - $meantime)] set sumdt [expr $sumdt + $dt * $dt] set sumdr [expr $sumdr + ($v - $meanres) * $dt] } # slope is relative R change within window (Ohm/Ohm/10 min.) set buf(slope$c) [expr $sumdr / $sumdt / $meanres * [hval /calib/window]] set maxslope 0 foreach ch $channels { if {abs($buf(slope$ch)) > abs($maxslope)} { set maxslope $buf(slope$ch) } } set dt [expr round($time - [lindex $buf($c) 0])] } if {$dt > $w / 3} { # do not update maxslope before we have a third of the window updateval /calib/maxslope [expr abs($maxslope)] } if {$dt < $w || abs($maxslope) > [hval /calib/slopelimit]} { # do not finish waiting mode before we have the full window and the slope limit is reached return } calib phase [hval /calib/phase] # switch to measuring mode calib mode 1 } else { if {abs([hval /tt] - [hval /tt/set]) / [hval /tt/set] > [hval /calib/tolerance] && [hval /tt] > 1.5} { # go to back to waiting mode when out of relative tolerance and not at base clientput "tt out of tolerance - WAIT" calib mode 0 return } set phase [hval /calib/phase] if {$phase >= 3} { # after 3 measuring cycles go to next point calib phase 0 calib tindex [expr [hval /calib/tindex] + 1] calib mode 0 return } set scanpos [hval /calib/scanpos] set lim [hval /calib/nmean] if {$scanpos % 2 == 0} { if {$c != [lindex $channels 0]} { return } } else { if {$c != [lindex $channels [expr ($scanpos + 1)/ 2]]} { return } } lappend buf($c) $value if {$scanpos == 0} { set lim [expr $lim / 2] } set len [llength $buf($c)] if {$len < $lim} { return } set mean [expr [::tcl::mathop::+ {*}$buf($c)] / double($len)] set sum2 0 set diflog [list] foreach r $buf($c) { set sum2 [expr $sum2 + pow($r - $mean, 2)] lappend diflog [format %.7g [expr $r - $mean]] } set sigma [expr sqrt($sum2) / double($len)] if {$scanpos == 0} { } elseif {$scanpos % 2 == 0} { set cf [lindex $channels [expr $scanpos/ 2]] set buf(ref$cf) $mean set buf(ref_sigma$cf) $sigma # keep last half of buffer for next time set buf($c) [lrange $buf($c) [expr $len / 2] end] } else { set buf(value$c) $mean set buf(sigma$c) $sigma set buf(time$c) $time set buf($c) [list] } incr scanpos if {$scanpos <= 2 * [llength $channels] - 2} { calib scanpos $scanpos return } catch { foreach channel $channels { if {$channel != [lindex $channels 0]} { set filnam [format [hval /calib/basename] [hval /calib/startdate] $phase $channel] set exists [file exists $filnam] set fil [open $filnam a] if {!$exists} { puts $fil "#zdat (zcalib by M.Zolliker 2018)" puts $fil "#time since midnight, R ref, R meas, sigma, Tset(nominal)" } puts $fil [format "%.2f %.9g %.9g %.9g" $buf(time$channel) $buf(ref$channel) $buf(value$channel) $buf(sigma$channel) [result tt set]] close $fil set filnam "[lindex [split [hval /calib/basename] "%"] 0]_[hval /calib/startdate].txt" set exists [file exists $filnam] set fil [open $filnam a] if {!$exists} { puts $fil "#zcalib (format 2021 M.Zolliker)" puts $fil "#chan, time since midnight, R ref, R meas, sigma, Tset(nominal)" } puts $fil [format "%d %.9g %.9g %.9g %.2f" $channel $buf(ref$channel) $buf(value$channel) $buf(sigma$channel) [result tt set] $buf(time$channel)] close $fil } } } msg if {$msg ne ""} { clientput "save error: $msg" } clear_buf incr phase calib phase $phase } } msg if {$msg ne ""} { clientput "ERROR: $msg" } } proc zcalib::set_chan {channel} { _res updatescript /res/s$channel/raw zcalib::update_chan hsetprop /res/s$channel/raw count 10 hsetprop /res/s$channel/raw dif 1 } proc zcalib::set_calchan {channel} { _res updatescript /res/s$channel/raw zcalib::update_chan hsetprop /res/s$channel/raw count 5 hsetprop /res/s$channel/raw dif 1 } proc zcalib::install_scripts {} { set channels [hval [sct]/channels] foreach channel $channels { _res updatescript /res/s$channel/raw zcalib::update_chan } hsetprop /res @calchan [lindex $channels 0] } proc zcalib::command {} { switch [sct target] { 0 { #run # update n_temp hset [sct]/tlist [hval [sct]/tlist] set today [clock format [clock seconds] -format "%Y-%m-%d"] hupdate [sct]/startdate $today hupdate [sct]/tindex 0 sct @basetime [clock scan $today] install_scripts nv autoflow/getTemp zcalib::tmts hset [sct]/tindex 0 } 1 { #pause activate_nv res autoscan 1 calib mode 0 } 2 { #continue nv 0 calib tindex [hval /calib/tindex] calib mode 0 sct update 0 install_scripts return idle } 3 { # stop nv autoflow/getTemp ccu4flow::tmts foreach channel [hval [sct]/channels] { _res killupdatescript /res/s$channel/raw zcalib::update_chan } } } sct update [sct target] return idle } proc zcalib::tmts {} { set tm [silent 1 result tt tm] set ts [silent $tm hval /res/s[hgetpropval /res @calchan]] if {$ts < $tm} { return $ts } else { return $tm } }