
namespace eval ccu4ext {
}

# automatic fill with level reading from external source
proc stdConfig::ccu4ext {type {readlevel 0}} {
  
  variable node
  variable ctrl

  controller syncedprot
  
  obj fillccu wr -int
  default -1
  prop write ccu4ext::writeExt $type
  prop read ccu4ext::fillExt $type
  
  prop enum watching=0,filling=1,inactive=2,manualfill=3
  prop label "filling state"

  if {$type eq "n2"} {
    set text LN2
  } else {
    set text LHe
  }
  kids "$text fill settings" {

    node state upd -text

    node readlevel -text par $readlevel
    prop visible false
    node lowlevel par 10
    node highlevel par 100
    
    node smooth upd
    prop fmt %.7g
    default -1

    node minfillminutes par 3.0
    node maxfillminutes par 30.0
    node minholdhours par 12.0
    node maxholdhours par 120.0
    node tolerance par 20.0
    node badreadingminutes par 30.0
    node tubecoolingminutes par 3.0
    if {$type eq "he"} {
      node vessellimit par 10.0
      node vext upd
    }
  }
  set node /sics/$ctrl
  return "$text fill with CCU4 and external level meter"
}

proc ccu4ext::changestate {type state} {
  switch $type {
    n2 {
      set cmd nc
      set auto na
    }
    he {
      set cmd hcd
      set auto ha
    }
    default {
      error "illegal level meter type: $type"
    }
  }
  switch $state {
    stop {
      cc $cmd 0
      cc $auto 1
    }
    fill {
      clientlog FILL$type
      cc $cmd 1
      cc $auto 1
    }
    off {
      cc $cmd 2
      cc $auto 0
    }
    watch {
      cc $cmd 3
      cc $auto 1
    }
  }
  sct change_time [DoubleTime]
}

proc ccu4ext::calcsmooth {level minspeed maxspeed} {
  if {![string is double $level]} {
    return 999.9
  }
  set now [clock seconds]
  set lasttime [silent 0 sct lasttime]
  if {$lasttime == 0} {
    set lasttime $now
  }
  set delta [expr $now - $lasttime]
  sct lasttime $now
  set smooth [hvali [sct]/smooth]
  if {$smooth < 0} {
    set smooth $level
  }
  set gs [expr $smooth + $delta * $maxspeed]
  if {$level > $gs} {
    set smooth $gs
  } else {
    set gs [expr $smooth + $delta * $minspeed]
    if {$level < $gs} {
      set smooth $gs
    } else {
      set smooth $level
    }
  }
  set badtime [silent 0 sct badtime]
  if {abs($smooth - $level) > [hvali [sct]/tolerance]} {
    if {$badtime < [hvali [sct]/badreadingminutes] * 60 * 2} {
      sct badtime [expr $badtime + $delta]
    }
  } else {
    if {$badtime > $delta} {
      sct badtime [expr $badtime - $delta]
    } else {
      sct badtime 0
    }
  }
  hupdate [sct]/smooth $smooth
  return $smooth
}

proc ccu4ext::setmode {type state} {
  if {[sctval [sct]] != $state} {
    sct target $state
    writeExt $type 0
  }
}

proc ccu4ext::fillExt {type} {
  if {$type eq "n2"} {
    set valve [sctval /cc/nv]
    set auto [sctval /cc/na]
  } else {
    set valve [sctval /cc/hv]
    set auto [sctval /cc/ha]
  }
  set errtxt ""
  set sm [expr [DoubleTime] > [silent 0 sct change_time] + 10]
  switch -- $valve {
    0 {
      set txt "fill valve off"
      if {$sm} {
        if {$auto} {
          setmode $type 0
        } else {
          setmode $type 2
        }
      }
    }
    1 {
      set txt "filling"
      if {$sm} {setmode $type 1}
    }
    2 {
      set txt "no fill valve"
      if {$sm} {setmode $type 2}
    }
    3 {
      set errtxt timeout
    }
    4 {
      set errtxt timeout1
    }
    default {
      set errtxt "unknown error"
    }
  }
  if {$errtxt eq ""} {
    updateval [sct]/state $txt
    hupdate [sct]/status ""
  } else {
    hupdate [sct]/status $errtxt
    updateval [sct]/state $errtxt
  }
  if {[sctval [sct]] == 3} {  # manualfill
    changestate $type fill
    return idle
  }
  set level [silent invalid result [hvali [sct]/readlevel]]
  if {! [string is double $level]} {
    hsetprop [sct]/readlevel geterror "illegal $type level reading"
    return idle
  }
  set state [sctval [sct]]
  set now [clock seconds]
  set lowlevel [hvali [sct]/lowlevel]
  if {$lowlevel < 0.0} {
    hupdate [sct]/lowlevel 0.0
  }
  if {$lowlevel > 90.0} {
    hupdate [sct]/lowlevel 90.0
  }
  set highlevel [hvali [sct]/highlevel]
  if {$highlevel > 100.0} {
    hupdate [sct]/highlevel 100.0
  }
  if {$highlevel < $lowlevel + 10.0} {
    hupdate [sct]/highlevel [expr $lowlevel + 10.0]
  }
  switch -- $state {
    -1 { # initializing
      hupdate [sct]/status "automatic $type filling off"
      hset [sct] 0
      sct lasttime 0
      changestate $type watch
      return idle
    }
    0 { # watching
      set minspeed [expr - 100.0 / [hvali [sct]/minholdhours] / 3600.]
      set maxspeed [expr - 100.0 / [hvali [sct]/maxholdhours] / 3600.]
      set s [calcsmooth $level $minspeed $maxspeed]
      if {$s < $lowlevel} {
        if {[sct badtime] > [hvali [sct]/badreadingminutes] * 60} {
          hupdate [sct]/status "automatic $type filling off - continuos bad reading"
          hset [sct] 2
          changestate $type off
          return idle
        }
        # start fill
        hset [sct] 1
        clientput "$type level low - start fill"
      } else {
        changestate $type stop
      }
      return idle
    }
    1 {
      set vcmd [silent 0 sct vessel_cmd]
      # check that vessel command works
      if {$vcmd ne "0" && [catch {[result $vcmd]}]} {
         clientput "WARNING: He vessel reading is configured, but not connected"
         sct vessel_cmd 0
         set vmd 0
      }
      set now [clock seconds]
      set s [hvali [sct]/smooth]
      sct minlevel [silent 999 sct minlevel]
      if {$s < [sct minlevel]} {
        sct minlevel $s
      }
      set vessel 999
      set maxspeed [expr 100.0 / [hvali [sct]/minfillminutes] / 60.]
      if {[sct tubecooling] && $now < [sct startfill] + [hvali [sct]/tubecoolingminutes] * 60 && $s < [sct minlevel] + 5} {
        # allow fill tube to get cold
        set minspeed [expr - 100.0 / [hvali [sct]/maxfillminutes] / 60.]
      } else {
        if {[sct tubecooling]} {
          sct tubecooling 0
          if {$vcmd ne "0"} {
            sct vstart_level [expr [result $vcmd]]
            # start time - 2 minutes
            sct vstart_time [expr $now - 120]
            sct vlast_time $now
            hupdate [sct]/vext [sct vstart_level]
          }
        }
        set minspeed [expr + 100.0 / [hvali [sct]/maxfillminutes] / 60.]
        if {$vcmd ne "0"} {
          set dt [expr $now - [sct vlast_time]]
          if {$dt > 0} {
            set slope [expr ([hvali [sct]/vext] - [sct vstart_level])/([sct vlast_time] - [sct vstart_time])]
          } else {
            set slope 0
          }
          set vessel [result $vcmd]
          set vext [expr [hvali [sct]/vext] + $slope * $dt]
          if {$vext < $vessel} {
            if {$vext < $vessel - 2.0} {
              set errcnt [silent 0 sct errcnt]
              incr errcnt
              if {$errcnt > 3} {
                hupdate [sct]/status "not enough progress on $type vessel - automatic ${type} filling off ($vext < $vessel - 2)"
                hset [sct] 2
                changestate $type off
                return idle
              }
              sct errcnt $errcnt
            } else {
              set vessel $vext
            }
          }
          hupdate [sct]/vext $vessel
          sct vlast_time $now
        }
      }
      if {$vessel < [silent 0 hvali [sct]/vessellimit]} {
        hupdate [sct]/status "$type vessel empty - automatic ${type} filling off"
        hset [sct] 2
        changestate $type off
        return idle
      }
      if {[calcsmooth $level $minspeed $maxspeed] <= $highlevel} {
        # continue filling
        changestate $type fill
        return idle
      }
      clientput "$type level high - stop fill"
      hset [sct] 0
      changestate $type watch
      return idle
    }
    2 {
      set minspeed [expr - 100.0 / [hvali [sct]/minholdhours] / 3600.]
      set maxspeed [expr + 100.0 / [hvali [sct]/minfillminutes] / 60.]
      calcsmooth $level $minspeed $maxspeed
    }
  }
  return idle
}

proc ccu4ext::writeExt {type {activate 1}} {
  sct update [sct target]
  hupdate [sct]/smooth [silent 0 result [hvali [sct]/readlevel]]
  switch -- [sct target] {
# watching
    0 {
      catch {logsetup [sct]/vext clear}
      hupdate [sct]/status ""
      eval [silent "expr 0" sct slow_cmd]
      if {$activate} {
        changestate $type watch
      }
      return idle
    }
# inactive
    2 {
      catch {logsetup [sct]/vext clear}
      eval [silent "expr 0" sct slow_cmd]
      if {[hvali [sct]/status] eq ""} {
        hupdate [sct]/status "automatic ${type} filling off"
      }
      clientput "ERROR: [hvali [sct]/status]"
      if {$activate} {
        changestate $type off
      }
      return idle
    }
# fill
    1 {
      hupdate [sct]/status ""
      set vcmd [silent 0 sct vessel_cmd]
      if {$vcmd ne "0"} {
        set vlev [silent 60 result $vcmd]
        if {$vlev == 60} {
          clientput "WARNING: vessel level meter not connected - proceed without it"
          sct vessel_cmd 0
        } elseif {$vlev < [hvali [sct]/vessellimit] + 3.0} {
          hupdate [sct]/status "vessel not full enough - automatic ${type} filling off"
          clientput [hvali [sct]/status]
          sct update 2
          if {$activate} {
            changestate $type off
          }
          return idle
        }
      }
      sct minlevel 100
      sct errcnt 0
      sct startfill [clock seconds]
      sct tubecooling 1
      # eval [split [silent "expr 0" sct fast_cmd]]
      eval [silent "expr 0" sct fast_cmd]
      if {$activate} {
        changestate $type fill
      }
      return idle
    }
# manualfill
    3 {
      changestate $type fill
    }
  }
  return idle
}
