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