namespace eval ccu {
  variable bits
  variable pcnt 0  
# bits to be kept / bits to be set
  array set bits {
    pulsOn  {0xfe 1}
    pulsOff {0xfe 0}
    n2On    {0xfc 1}
    n2Off   {0xfc 2}
    heOn    {0xfa 1}
    heOff   {0xfa 4}
    nvOpen  {0xf6 9}
    nvClose {0xf6 1}
    nvOff   {0x0e 0xc0}
    weak    {0xcf 0x20}
    normal  {0xcf 0x10}
    strong  {0xcf 0x30}
    micro   {0x3f 0xc0}
    short   {0x3f 0x80}
    long    {0x3f 0x00}
  }
}

# general ccu initialization
proc stdConfig::ccuInit {} {
  variable node
  variable ctrl
  variable name
  
  if {[controller std "0x0d" 2]} {
    controllerDesc "CCU cryostat control unit"
    prop resolution 26.214
    prop dout 0x06
    prop readA0 0
    prop readA1 0
    prop readA2 0
    prop readA3 0
    prop readA4 0
    prop readA5 0
    prop readA6 0
    prop readA7 0
    prop starttime 0
    prop n2off ccu::valveoff n2
    prop n2on ccu::valveon n2
    prop heoff ccu::valveoff he
    prop heon ccu::valveon he
    prop beforefixed 0
    set node /sics/$ctrl/tasks
    poll 1
    prop start ccu::start
    prop read ccu::handler
  }
  set node /sics/$ctrl
}

# automatic n2 fill with two pt sensors
proc stdConfig::n2_ccu {{title ""} {type n2cryo}} {
  variable name
  
  ccuInit
  # A0: upper sensor, A1: lower sensor
  prop readA0 1
  prop readA1 1
  prop n2path /$name
  if {$type eq "trap"} {
    prop warn_trap_empty 1
    set vis false
  } else {
    prop warn_no_auto 1
    set vis true
  }

  obj n2ccu out -int
  default -1
  prop write ccu::write
  
  prop enum watching=0,filling=1,inactive=4
  prop visible $vis
  prop label "filling state"

  if {$title eq ""} {
    set title "LN2 fill settings"
  }

  kids $title {

    node threshold par 90
    prop help {[K] a sensor below this value is immersed}
    prop visible $vis

    node limit par 240
    prop label "warm limit"
    prop help {[K] above this value the cryo is considered as warm}
    prop visible $vis

    node tmo1 par 300
    prop label "max. tube cooling time"
    prop help {[sec]}
    prop visible $vis

    node tmo2 par 1800
    prop label "max. fill time"
    prop help {[sec]}
    prop visible $vis

    node upper upd
    node lower upd
  }
  return "LN2 fill with CCU"
}

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

  ccuInit

  obj fillccu out -int
  default -1
  prop write ccu::writeExt $type
  
  prop enum watching=0,filling=1,inactive=2
  prop label "filling state"

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

    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
}

proc stdConfig::n2_ext_ccu {} {
  variable name

  ccuExtInit n2 "lev n2"
  prop n2extpath /$name
  prop n2fill "ccu::fillExt n2"
  return "LN2 fill with CCU and external level meter"
}

proc stdConfig::he_ext_ccu {{readlevel lev}} {
  variable name

  ccuExtInit he $readlevel
  prop heextpath /$name
  prop hefill "ccu::fillExt he"
  return "LHe fill with CCU and external level meter"
}

proc stdConfig::he_ccu {} {
  variable name
  
  ccuInit
  # A3: he sensor
  prop readA3 1
  prop hepath /$name
  # voltage divider 56.2 kOhm + 200 kOhm, max. 10 V
  # full is ~ 2.2 V
  prop full [expr 56.2 / 256.2 * 10]
  prop empty 0

  obj heccu upd
  return "He reading from AMI135/136 with CCU"
}
  
proc stdConfig::he9_ccu {} {
  variable name
  
  ccuInit
  # A6: he sensor (potentiometer channel)
  prop readA6 2
  prop hepath /$name
  # on this level meter, full is 0, empty is 0.5 V
  prop full 0
  prop empty 0.5

  obj heccu upd
  return "He reading from old MA09 with CCU"
}
  
proc stdConfig::n2cool_ccu {} {
  variable name
  variable node
  variable ctrl

  ccuInit

  obj fillccu out -int
  default -1
  prop write ccu::writePrecool

  prop enum watching=0,filling=1,inactive=2
  prop label "precool filling state"

  return "LN2 precool with CCU"
}

proc stdConfig::nv_ccu {{vtype VTI}} {
  variable name

  ccuInit
  # A2: pressure sensor
  prop readA2 1
  prop nvpath /$name

  obj nvccu out -int
  default 0
  prop control ccu::control
  prop write ccu::writeNv
  if {$vtype != "VTI"} {
    # lambda point refrigerator
    prop enum "off=5,fixed=0,controlling=1,close=3,open=4"
    prop vtype $vtype
  } else {
    prop enum "automatic=2,fixed=0,controlling=1,close=3,open=4"
    prop vtype $vtype
  }
  poll 0.5 progress ticker
  prop ticker ccu::ticker

  kids "$vtype needle valve settings" {
    node set out
    default 2
    prop write ccu::writeSet
    prop label "flow set"

    node flowmax par 20
    prop label "flow maximum"
    
    node prop par 200
    
    node int par 10
    
    node minpulse par 100
    prop label "minimum pulse length:"
    prop enum "micro=1,short=10,long=100"
    prop help "micro: 1 ms, short: 10 ms, long: 100 ms"
    
    node tolerance par 0.05
    
    node speed upd
    
    node flow upd
    
    node pos upd
    default 20
    
    kids "position estimation parameters" {
      node norm par 20
      node max par 60
    }
    
    node autoflow -none
    kids "autoflow control parameters" {
      flow::make
    }
  }
  return "$vtype needle valve control with CCU"
}

proc stdConfig::nvread_ccu {} {
  variable name

  ccuInit
  # A2: pressure sensor
  prop readA2 1
  prop nvpath /$name
  prop readonly 1

  obj nvccu rd -int
  prop read ccu::flowtask
  prop enum "readonly=0"
  
  kids "pressure reading" {
    node flow upd
  }
}

proc ccu::flowtask {} {
  sct update 0
  namespace eval :: {
    flowtask
  }
  return idle
}

proc ccu::start {} {
# set all digital ports to output push-pull and all outputs inactive
# remark: bit 1 and 2 are inactive High (hex 06)
  sct send ":10000000030600FF00FF0006.."
  return ccu::setADCrate
}

proc ccu::setADCrate {} {
# set adc rate to 8
  sct send ":10000D0001020008.."
  return ccu::getidn
}

proc ccu::getidn {} {
  sct send ":0300030001.."
  return stdSct::completeStart
}

proc ccu::handler {} {
  variable last_nv 0
  variable last_n2 0

  set now [clock seconds]
  if {$now/5 != $last_n2/5} {
    set last_n2 $now
    # read all used channels
    set chanlist {0 1 2 3 4 5 6 7}
  } elseif {[sct readA2]} {
    set last_nv $now
    # read only used channels for needle valve control
    set chanlist {2 6 7}
  }
  set min 8
  set max -1
  foreach chan $chanlist {
    if {[sct readA$chan]} {
      if {$chan > $max} {
        set max $chan
      }
      if {$chan < $min} {
        set min $chan
      }
    }
  }
  if {[silent 0 sct hefill] ne 0} {
    [sct controllerName] queue [sct heextpath] read hefill
  }
  if {[silent 0 sct n2fill] ne 0} {
    [sct controllerName] queue [sct n2extpath] read n2fill
  }
  if {$min > $max} {
    return idle
  }

  set n [expr $max - $min + 1]
  sct firstChan $min
  sct lastChan $max
  sct nChan $n
  sct send [format ":04%4.4x%4.4x.." $min $n]

  return ccu::update
}

proc ccu::smooth {var band old} {
  upvar $var val
#clientput "$val $band $old"
  set weight [expr 0.05 + pow(($val - $old) / $band, 2)]
#clientput "w $weight"
  if {$weight < 1.0} {
    set val [expr $old + ($val - $old) * $weight]
  }
#clientput "$weight $val"
}

proc ccu::convertPt2T {sensor raw} {
  set millivolt [expr $raw / [sct resolution]]
  if {$millivolt < 30} {
    hupdate [sct n2path]/status "LN2 sensor unplugged"
    hsetprop [sct n2path]/$sensor geterror "LN2 sensor unplugged"
    return 0
  }
  if {$millivolt < 150 || $millivolt > 1500} {
    hupdate [sct n2path]/status "illegal LN2 sensor reading: $millivolt mV"
    hsetprop [sct n2path]/$sensor geterror "illegal LN2 sensor reading: $millivolt mV"
    return 0
  }
  set t [expr 68 * (5000.0 / $millivolt - 1.0) * 0.25 + 26.8]
  catch { ccu::smooth t 5.0 [hvali [sct n2path]/$sensor] }
  hupdate [sct n2path]/$sensor $t
  hdelprop [sct n2path]/$sensor geterror
  return $t
}

proc ccu::nvstatus {} {
  set status ""
  set vtype [hgetpropval [sct nvpath] vtype]
  if {[silent -63 hvali [sct nvpath]/flow] < -50} {
    append status "$vtype needle valve pressure sensor not connected\n"
  } elseif {[silent -1 hvali [sct nvpath]] == -1} {
    append status "$vtype needle valve fixed\n"
  }
  set pos [silent 20 hvali [sct nvpath]/pos]
  if {$pos >= [hvali [sct nvpath]/pos/max]} {
    append status "$vtype needle valve probably open\n"
  }
  if {$pos <= 0} {
    append status "$vtype needle valve probably closed\n"
  }
  hupdate [sct nvpath]/status $status
}

proc ccu::update {} {
  set fmt [format ":04%2.2X%s" [expr 2 * [sct nChan]] [string repeat %4x [sct nChan]]]
  set vars [lrange {a0 a1 a2 a3 a4 a5 a6 a7} [sct firstChan] [sct lastChan]]
  if {[eval scan [sct result] $fmt $vars] != [sct nChan]} {
    error "bad response to '[sct send]': '[sct result]'"
  }
  if {[sct readA2]} {
    # reading 0..65535, voltage 0..2500 mV with a 100 Ohm resistor: 0..25 mA
    # 4..20 mA is 0..250 mbar
    set flow [expr ($a2 / 65535.0 * 25 - 4) / 16.0 * 250]
    catch { ccu::smooth flow 0.5 [silent 0 hvali [sct nvpath]/flow] }
    hupdate [sct nvpath]/flow $flow
    if {$flow < -50} {
      hsetprop [sct nvpath]/flow geterror "pressure sensor not connected"
      switch [silent 0 hvali [sct nvpath]] {
        1 - 2 {
          hset [sct nvpath] 0
          hsetprop [sct nvpath]/flow geterror "pressure sensor not connected"
        }
      }
    } else {
      hdelprop [sct nvpath]/flow geterror
    }
    if {[silent 0 sct readonly] == 0} {
      ccu::nvstatus
      [sct controllerName] queue [sct nvpath] read control
    }
  }
  if {[sct readA6] == 2} {
    # He reading on chan 6 (for MA09)
    # reading 65535 is 2.5 V, output is in percent
    set val [expr 100.*(2.5*$a6/65535.-[sct empty])/([sct full]-[sct empty])]
    hupdate [sct hepath] $val
  } elseif {[sct readA3]} {
    # He reading on chan 3 (for AMI 135/136 level meter)
    # reading 65535 is 2.5 V, output is in percent
    set val [expr 100.*(2.5*$a3/65535.-[sct empty])/([sct full]-[sct empty])]
    if {$val < -1.0} {
      set val -1.0
    } elseif {$val > 101.0} {
      set val 101.0
    }
    hupdate [sct hepath] $val
  }
  if {[sct firstChan] == 0} {
    # automatic n2 fill with 2 point sensor
    set upper [convertPt2T upper $a0]
    set lower [convertPt2T lower $a1]
    
    set lim [hvali [sct n2path]/limit]
    set thr [hvali [sct n2path]/threshold]
    set state [hvali [sct n2path]]
    if {$upper > $lim || $lower > $lim} {
      if {$state < 3} {
        hupdate [sct n2path]/status "sensor warm"
        hupdate [sct n2path] 4
        hsetprop [sct n2path] enum watching=0,fill=3,inactive (sensor warm)=4
        return n2off
      } elseif {$state == 4} {
        hupdate [sct n2path]/status "sensor warm"
        hupdate [sct n2path] $state
        return idle
      }
    } elseif {[string equal "sensor warm" [result hvali [sct n2path]/status]]} {
      hupdate [sct n2path]/status ""
    }
    if {$upper < $thr && $lower > $thr} {
      if {$state < 4} {
        hupdate [sct n2path]/status "sensor upside down"
        hupdate [sct n2path] 4
        hsetprop [sct n2path] enum watching=0,fill=3,inactive (sensor upside down)=4
        return n2off
      } else {
        hupdate [sct n2path] $state
        return idle
      }
    } elseif {[string equal "sensor upside down" [result hvali [sct n2path]/status]]} {
      hupdate [sct n2path]/status ""
    }
    if {[silent 0 sct warn_trap_empty]} {
      set now [clock seconds]
      if {$lower > $thr} {
        if {[silent 0 sct empty_time] == 0} {
          sct empty_time $now
        }
        if {$now > [sct empty_time] + 600} {
          set hr [expr 20 - ($now - [sct empty_time]) / 3600]
          if {$hr <= 0} {
            set txt "the trap gets empty soon - please fill immediately"
          } else {
            set txt "the trap gets empty soon - please fill within $hr h"
          }
        } else {
          set txt ""
        }
      } else {
        set txt ""
        sct empty_time 0
      }
      if {$txt ne [silent 0 hvali [sct n2path]/status]} {
        hupdate [sct n2path]/status $txt
      }
    }
    switch -- $state {
      -1 { # initializing
        hupdate [sct n2path] 4
        hsetprop [sct n2path]  enum watching=0,fill=1,inactive=4
        if {[silent 0 sct warn_no_auto]} {
          hupdate [sct n2path]/status "automatic LN2 filling off"
        }
        return n2off
      }
      0 { # watching
        if {$lower > $thr} {
          clientput "start N2 fill"
          hupdate [sct n2path] 1
          hsetprop [sct n2path] enum watching=0,fill (starting)=1,inactive=4
          sct starttime [clock seconds]
          return n2on
        }
      }
      1 { # filling until lower or upper is cold
        if {[clock seconds] > [sct starttime] + [hvali [sct n2path]/tmo1]} {
          if {$lower < $thr} {
            hupdate [sct n2path] 2
            hsetprop [sct n2path] enum watching=0,fill=2,inactive=4
            return n2on
          }
          clientput "stop N2 fill (lower sensor not cold quick enough)"
          hupdate [sct n2path]/status "lower sensor not cold quick enough"
          hupdate [sct n2path] 4
          hsetprop [sct n2path]  enum watching=0,fill=3,inactive (lower sensor not cold quick enough)=4
          return n2off
        } else {
          if {$upper < $thr} {
            clientput "finished N2 fill"
            hupdate [sct n2path] 0
            hsetprop [sct n2path]  enum watching=0,fill=1,inactive=4
            return n2off
          }
        }
        sct update $state
        return n2on
      }
      2 - 3 { # filling until upper is cold
        if {[clock seconds] > [sct starttime] + [hvali [sct n2path]/tmo2]} {
          clientput "stop N2 fill (fill timeout)"
          hupdate [sct n2path]/status "fill timeout"
          hupdate [sct n2path] 4
          hsetprop [sct n2path]  enum watching=0,filling=2,inactive (fill timeout)=4
          return n2off
        } elseif {$upper < $thr} {
          clientput "finished N2 fill"
          hupdate [sct n2path] 0
          hsetprop [sct n2path] enum watching=0,fill=1,inactive=4
          return n2off
        }
        hupdate [sct n2path] $state
        return n2on
      }
    }
    hupdate [sct n2path] $state
  }
  return idle
}

proc ccu::output args {
  variable bits
  variable pcnt

  set dout [sct dout]
  foreach a $args {
    set b $bits($a)
    set dout [expr $dout & [lindex $b 0] | [lindex $b 1]]
  }
  sct dout $dout
  sct send [format ":10000200010200%2.2x.." $dout]
  if {[silent 0 result pulsedebug]} {
    if {[string equal pulsOn $args]} {
      incr pcnt
    } elseif {[string equal pulsOff $args]} {
    } else {
      clientput "$args ($pcnt)"
      set pcnt 0
    }
    clientput "[format "%2.2x" $dout]"
  }
}

proc ccu::valveoff {type} {
  ccu::output ${type}Off
  return stdSct::complete
}

proc ccu::valveon {type} {
  ccu::output ${type}On
  return ccu::pulse
}

proc ccu::pulse {} {
  ccu::output pulsOff
  return stdSct::complete
}

proc ccu::write {} {
  hupdate [sct n2path]/status ""
  switch -- [sct target] {
# watching
    0 {
      sct update [sct target]
      sct enum watching=0,fill=3,inactive=4
      return n2off
    }
# inactive
    4 {
      sct update 4
      sct enum watching=0,fill=3,inactive=4
      hupdate [sct]/status "automatic LN2 filling off"
      return n2off
    }
    1 - 2 - 3 {
      sct starttime [clock seconds]
      if {[hvali [sct]] < 3} {
        sct update 1
        sct enum watching=0,fill=1,inactive=4
      } else {
        sct update 3
        sct enum watching=0,fill=3,inactive=4
      }
      return idle
    }
  }
  return idle
}

# --- automatic fill with level reading from external source ---

proc calcsmooth {level minspeed maxspeed} {
  if {![string is double $level]} {
    return 999.9
  }
  set now [clock seconds]
  set delta [expr $now - [silent $now sct lasttime]]
  sct lasttime $now
# use property for better resolution
  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 ccu::fillExt {type} {
  set level [silent invalid result [hvali [sct]/readlevel]]
  if {! [string is double $level]} {
    hupdate [sct ${type}extpath]/status "illegal $type level reading"
    return idle
  }
  set state [hvali [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] 2
      sct lasttime 0
      return ${type}off
    }
    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
          return ${type}off
        }
        # start fill
        hset [sct] 1
        clientput "$type level low - start fill"
      }
      return ${type}off
    }
    1 {
      set vcmd [silent 0 sct vessel_cmd]
      set now [clock seconds]
      set s [hvali [sct]/smooth]
      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
                return ${type}off
              }
              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
        return ${type}off
      }
      if {[calcsmooth $level $minspeed $maxspeed] <= $highlevel} {
        # continue filling
        return ${type}on
      }
      clientput "$type level high - stop fill"
      hset [sct] 0
      return ${type}off
    }
    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 ccu::writeExt {type} {
  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]
      return ${type}off
    }
# 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]"
      return ${type}off
    }
# fill
    1 {
      hupdate [sct]/status ""
      set vcmd [silent 0 sct vessel_cmd]
      if {$vcmd ne "0"} {
        set vlev [result $vcmd]
        if {$vlev == 60} {
          hupdate [sct]/status "vessel level meter not connected - automatic ${type} filling off"
          clientput [hvali [sct]/status]
          sct update 2
          return ${type}off
        }
        if {$vlev < [hvali [sct]/vessellimit] + 3.0} {
          hupdate [sct]/status "vessel not full enough - automatic ${type} filling off"
          clientput [hvali [sct]/status]
          sct update 2
          return ${type}off
        }
      }
      sct minlevel 100
      sct errcnt 0
      sct startfill [clock seconds]
      sct tubecooling 1
      eval [silent "expr 0" sct fast_cmd]
      return ${type}on
    }
  }
  return idle
}

proc ccu::writePrecool {} {
  hupdate [sct]/status ""
  sct update [sct target]
  switch -- [sct target] {
# watching
    0 {
      return n2off
    }
# inactive
    2 {
      hupdate [sct]/status "LN2 precooling off"
      return n2off
    }
    1 {
      return n2on
    }
  }
  return idle
}


proc ccu::writeNv {} {
  if {[sct target] != 5} {
    eval [silent expr sct switchgraph] 1
  }
  hupdate [sct] [sct target]
  switch -- [sct target] {
    5 { # off
      eval [silent expr sct switchgraph] 0
      hupdate [sct]/speed 0
      ccu::output nvOff
      return stdSct::complete
    }
    0 { # fixed
      hupdate [sct]/speed 0
      ccu::output nvOff
      return stdSct::complete
    }
    1 - 2 { # controlled or automatic:
      sct beforefixed [sct target]
      ccu::output nvOff
      return stdSct::complete
    }
    3 { # close
      if {[hvali [sct]/pos] < [hvali [sct]/pos/norm]} {
        hupdate [sct]/pos [hvali [sct]/pos/norm]
      }
    }
    4 { # open
      if {[hvali [sct]/pos] > [hvali [sct]/pos/norm]} {
        hupdate [sct]/pos [hvali [sct]/pos/norm]
      }
    }
  }
  return idle
}

proc ccu::writeSet {} {
  switch -- [hvali [sct objectPath]] {
    0 - 3 - 4 - 5 {
      hset [sct objectPath] [sct beforefixed]
    }
  }
  sct update [sct target]
  return idle
}

proc ccu::adjustpos {delta} {
  set pos [hvali [sct]/pos]
  set normpos [hvali [sct]/pos/norm]
  if {$pos < $normpos} {
    if {$delta < 0} {
      if {$pos <= 0} {
        set pos 0
      } else {
        set pos [expr $pos + $delta * 0.2]
      }
    } else {
      set pos [expr $pos + $delta]
    }
  } else {
    if {$delta > 0} {
      set pos [expr $pos + $delta * 0.5]
    } else {
      set pos [expr $pos + $delta]
    }
  }
  if {$pos > [hvali [sct]/pos/max]} {
    set pos [hvali [sct]/pos/max]
  }
  hupdate [sct]/pos $pos
}

proc ccu::trans args {
  # transform all variables listed in the args
  foreach var $args {
    upvar $var val
    if {$val > 4.0} {
      set val [expr 3.3 + ($val - 4.0) * 0.1]
    } elseif {$val > 3.5} {
      set val [expr 3.2 + ($val - 3.5) * 0.2]
    } elseif {$val > 3.0} {
      set val [expr 3 + ($val - 3) * 0.4]
    }
  }
}

# polling script for nv control
proc ccu::control {} {
  switch -- [hvali [sct]] {
    0 - -1 - 5 { # fixed or off
      if {[sct beforefixed] == 0} {
        sct beforefixed 2
      }
      logsetup [sct]/set clear
      set speed 0
      return idle
    }
    3 { # closing
      set soll -1
      set speed -1000
    }
    4 { # opening
      set soll -1
      set speed 1000
    }
    1 { # controlled
      set soll [hvali [sct]/set]
      # the following line is for graphic (using flowtarget as nv_set curve)
      catch {hupdate [sct]/autoflow/flowtarget $soll}
    }
    2 { # automatic
      flow::task /nv/autoflow [hvali [sct]/set] [hvali [sct]/flowmax]
      set soll [hvali [sct]/autoflow/flowset]
    }
    default {
      return idle
    }
  }

  if {$soll >= 0} {
    # case 1 and 2: pid control
    set ist [hvali [sct]/flow]
    set tol [hvali [sct]/tolerance]

    if {$soll > $ist + $tol} {
      set soll [expr $soll - $tol]
    } elseif {$soll < $ist - $tol} {
      set soll [expr $soll + $tol]
    } else {
      set soll $ist
    }
    # transfer function taking into account heavy nonlinearities of needle valve
    ccu::trans ist soll

    set prop [hvali [sct]/prop]
    set int [hvali [sct]/int]
    set diff [expr $soll - $ist]
    set lastdiff [silent $diff sct lastdiff]
    sct lastdiff $diff
    # carry is used for finetuning when speed is below 1
    set carry [silent 0 sct carry]
    set speed [expr $prop * ($diff - $lastdiff) + $int * $diff + $carry]

  } else {
    logsetup [sct]/set clear
  }
  set long 100
  set short 10
  set micro 1  
  set minpulse [hvali [sct]/minpulse]
  if {$speed > 700} {
    set speed 700
  } elseif {$speed < -700} {
    set speed -700
  }
#  clientput $speed
  set normpos [hvali [sct]/pos/norm]
  set maxpos [hvali [sct]/pos/max]
  set pos [hvali [sct]/pos]
  if {$speed < 0} {
    set dir nvClose
    set plen [expr -$speed]
    set force normal
  } else {
    set dir nvOpen
    set plen $speed
    if {$pos < $normpos} {
      set force strong
    } else {
      set force normal
    }
  }
  sct pulser ccu::pulser
  if {$plen >= $long * 0.9} {
    set length long
    sct utime endtime
    set p [expr int($plen / $long)]
    if {$p < 1} {
      set p 1
    }
    sct pulsecnt $p
    sct endtime [expr [sct endtime] + $plen * 0.001]
    sct plen $plen
    if {$plen == 700} {
      # a pulse of more than 700 ms is extended to a 1000 ms hardware pulse
      set plen 1000
      sct pulser ccu::pulsend
    } else {
      sct pulser ccu::timer
    }
  } elseif {$plen >= $short * 0.9 && $minpulse <= 10} {
    set length short
    set p [expr int($plen / $short)]
    if {$p > 5} {
      set p 5
    } elseif {$p < 1} {
      set p 1
    }
    sct pulsecnt $p
    set plen [expr $p * $short] 
  } elseif {$plen >= $micro * 0.9 && $minpulse <= 1} {
    set length micro
    set force strong
    set p [expr int($plen / $micro)]
    if {$p > 5} {
      set p 5
    } elseif {$p < 1} {
      set p 1
    }
    sct pulsecnt $p
    set plen [expr $p * $micro] 
  } else {
    sct carry $speed
    hupdate [sct]/speed 0
    return idle
  }
  sct carry 0
  if {$speed < 0} {
    set speed -$plen
  } else {
    set speed $plen
  }
  hupdate [sct]/speed $speed
  ccu::output $dir $force $length
  ccu::adjustpos [expr $speed * 0.001]
  return pulser
}

proc ccu::timer {} {
  sct utime nowtime
  if {[sct nowtime] > [sct endtime]} {
    ccu::output nvOff
    return stdSct::complete
  }
  ccu::output pulsOff
  return ccu::timer
}

proc ccu::pulser {} {
  set p [sct pulsecnt]
  incr p -1
  sct pulsecnt $p
  if {$p <= 0} {
    ccu::output nvOff
    return stdSct::complete
  }
  ccu::output pulsOff
  return ccu::pulser2
}

proc ccu::pulser2 {} {
  ccu::output pulsOn
  return ccu::pulser
}

proc ccu::ticker {} {
  if {([sct dout] & 0x30) == 0} {
    # do not tick when motor is off
    return idle
  }
  ccu::output pulsOn
  return ccu::pulsend
}

proc ccu::pulsend {} {
  ccu::output pulsOff
  return stdSct::complete
}

proc ccu::unpoll {} {
  return unpoll
}
