# software pi loop
# new version (old version has strange parameters)

namespace eval piloop {
}

if {![namespace exists trun]} {
  source drivers/trun.tcl
}

proc stdConfig::piloop args {
  variable name
  variable path
  variable node
  
  scanargs $args var -invar -outvar -outmin 0 -outmax 1 -prop 1 -int 10 \
    -maxdelta 60 -precision 0.001 -inpfunc 0 -outfunc 0 -title $name \
    -custinp log -custout log -span 0 -deadband 0 \
    -outlinear 1e-6 -inplinear 1e-3 -ramptime 0 -smoothtime 0 -ramptol 0.05

# custinp, custout:
#   custom input / output function
#   log: allow choice between linear and logarithmic
#   none: only linear
#   else: a custom function. the last arguments are either
#      <value>                  forward conversion
#      inv <value>              inverted conversion

  variable hostport none
  controller dumprot

  if {$span == 0} {
    if {$custout eq "none"} {
      set span [expr $outmax - $outmin]
    }
  }

  obj loop -drive out
  prop lastinput 0
  prop label setpoint
  prop custinp $custinp
  prop custout $custout
  prop span [expr double($span)]

  kids "$title control parameters" {
    node mode -int out
    default 0
    prop enum off,on
    prop check piloop::checkmode
    prop write stdSct::completeUpdate

    node reg upd
    prop geterror "off"
    
    node output out
    default $outmin
    prop check piloop::checkoutput
    prop write stdSct::completeUpdate

    node ramptime par $ramptime
    prop help {time for ramping [sec] deltax = 1 or a factor 10}

    node ramptol par $ramptol
    prop help {log of max. deviation when ramping}

    node smoothtime par $smoothtime
    prop help {time for smoothing ramp near setpoint}

    node invar -text out
    default 0
    prop check piloop::setinvar
    prop write stdSct::completeUpdate
    prop visible false
    
    node outvar -text par $outvar
    prop visible false
    
    node prop par $prop
    prop help {smaller means more sensitive. a change of 'prop' on input\
               -> a change of 100 % or a factor 10 on output}

    node int par $int
    prop help "integration time (sec)"

    node outmin par $outmin
    prop help "output for maximal decrease of input var."

    node outmax par $outmax
    prop help "output for maximal increase of input var."
    
    if {$custinp eq "log"} {
      node inpfunction par -int $inpfunc
      prop label "input function"
      prop enum linear,logarithmic

      node inplinear par $inplinear
      prop help "function gets linear below about this value"
    }

    if {$custout eq "log"} {
      node outfunction par -int $outfunc
      prop label "output function"
      prop enum linear,exponential

      node outlinear par $outlinear
      prop help "function gets linear below about this value"
    }

    node precision par $precision
    prop label "output precision"
    
    node deadband par $deadband

    node maxdelta par $maxdelta
  }
  stdConfig::tdrive "$name control parameters" self piloop::settarget
#  hset $path/setcmd piloop::settarget
  hset $path/invar $invar
}

proc piloop::checkmode {} {
  if {[sct target] == 0} {
    set outmin [hvali [sct parent]/outmin]
    set effoutput [silent $outmin eval "result [hvali [sct parent]/outvar]"]
    if {$effoutput > $outmin} {
      hset [sct parent]/output $outmin
    }
    updateerror [sct parent]/reg off
  } elseif {[hvali [sct]] == 0} {
    hupdate [sct parent]/reg [hvali [sct parent]]
    set outvar_start [silent "" sct @outvar_start]
    if {$outvar_start ne ""} {
      eval $outvar_start
    }
  }
}

proc piloop::updateinput {value} {

  set maxdelta [hvali [sct]/maxdelta]
  set now [DoubleTime]
  if {[sct lastinput] == 0} {
    set deltat 1
  } else {
    set deltat [expr $now - [sct lastinput]]
    if {$deltat > $maxdelta} {
      set deltat $maxdelta
    }
  }
  set timestep [silent 1 sct timestep]
  if {$deltat < $timestep * 0.9 && $timestep > 0.01} {
    set timestep [expr $timestep * 0.9]
  } else {
    set timestep $deltat
  }
  sct timestep $timestep
  sct lastinput $now
  if {[hvali [sct]/mode] == 0} {
    return idle
  }

  set prop [hvali [sct]/prop]
  set int [hvali [sct]/int]
  set outmin [hvali [sct]/outmin]
  set outmax [hvali [sct]/outmax]
  set precision [hvali [sct]/precision]
  set ramptime [hvali [sct]/ramptime]
  set smoothtime [hvali [sct]/smoothtime]
  set reg [hvali [sct]/reg]
  set input $value
  set soll [hvali [sct]/set]
  set deadband [hvali [sct]/deadband]
  if {abs($soll - $input) <= $deadband} {
    return idle
  }

  set effoutput [eval "result [hvali [sct]/outvar]"]
  set output [hvali [sct]/output]
  set span [sct span]
  if {[sct custout] eq "log"} {
    set outlinear [hvali [sct]/outlinear]
    set outfunction [hvali [sct]/outfunction]
    if {$outfunction == 1} {
      set output [expr log10($output + $outlinear)]
      set effoutput [expr log10($effoutput + $outlinear)]
      if {$span == 0} {
        set span 1.0
      }
    } else {
    }
  } elseif {[sct custout] ne "none"} {
    set output [eval [sct custout] inv $output]
    set effoutput [eval [sct custout] inv $effoutput]
  }
  set lastdif [silent 0 sct lastdif]
  if {$span == 0} {
    set span [expr double($outmax - $outmin)]
  }

  set otol [silent $precision sct otol]
  if {$output > $effoutput + $otol} {
#clientput "$otol [expr $output - $effoutput + $otol]"
    set output [expr $effoutput + $otol]
  } elseif {$output < $effoutput - $otol} {
#clientput "$otol [expr $output - ($effoutput - $otol)]"
    set output [expr $effoutput - $otol]
  }

  if {[sct custinp] eq "log"} {
    set inplinear [hvali [sct]/inplinear]
    set inpfunction [hvali [sct]/inpfunction]
    if {$inpfunction == 1} {
      set soll [expr log10($soll + $inplinear)]
      set input [expr log10($input + $inplinear)]
      set reg [expr log10($reg + $inplinear)]
    }
  } elseif {[sct custinp] ne "none"} {
    set soll [eval [sct custinp] $soll]
    set input [eval [sct custinp] $input]
    set reg [eval [sct custinp] $reg]

  }

  if {$ramptime == 0} {
    set reg $soll
  } else {
    set step [expr $deltat / double($ramptime)]
    set dist [expr abs($reg - $soll)]
    if {$dist * $ramptime < $smoothtime } {
      set step [expr $step * sqrt($dist * $ramptime / $smoothtime)]
    }
    set tol [hvali [sct]/ramptol]
    if {$tol * $ramptime < $timestep} {
      set tol [expr $timestep/double($ramptime)]
    }
    if {$soll > $input} {
      set newreg [expr $reg + $step]
      if {$newreg > $input + $tol} {
        set newreg [expr $input + $tol]
      }
      if {$newreg > $soll} {
        set reg $soll
      } elseif {$newreg > $reg} {
        set reg $newreg
      }
    } else {
      set newreg [expr $reg - $step]
      if {$newreg < $input - $tol} {
        set newreg [expr $input - $tol]
      }
      if {$newreg < $soll} {
        set reg $soll
      } elseif {$newreg < $reg} {
        set reg $newreg
      }
    }
  }
  set dif [expr double($reg - $input)]
  set l [llength $lastdif]
  set difdif [expr ($dif - [lindex $lastdif 0]) / $l]
  lappend lastdif $dif
  sct lastdif [lrange $lastdif end-5 end]
  
  set do [expr $span/double($prop) * ($difdif + $deltat * $dif / double($int))]
  set output [expr $output + $do]
#clientput [format "out %12.5g dd %12.5g dtd %12.5g  s/p %12.5g" $output $difdif [expr $deltat * $dif / double($int)] [expr $span/double($prop)]]

  sct otol [expr 2 * abs($do) + $precision]

  if {[sct custinp] eq "log"} {
    if {$inpfunction == 1} {
      set reg [expr pow(10, $reg) - $inplinear]
    }
  } elseif {[sct custinp] ne "none"} {
    set reg [eval [sct custinp] inv $reg]
  }
  updateval [sct]/reg $reg
  if {[sct custout] eq "log"} {
    if {$outfunction == 1} {
      set output [expr pow(10, $output) - $outlinear]
#clientput $output
    }
  } elseif {[sct custout] ne "none"} {
    set output [eval [sct custout] $output]
  }
  if {$outmax > $outmin} {
    if {$output > $outmax} {
      set output $outmax
    } elseif {$output < $outmin} {
      set output $outmin
    }
  } else {
    if {$output > $outmin} {
      set output $outmin
    } elseif {$output < $outmax} {
      set output $outmax
    }
  }
  if {[catch {hset [sct]/output $output} msg]} {
    hsetprop [sct]/reg geterror $msg
  }
  return idle
}

# updatescript on the remote input node
proc piloop::updateinput1 {value} {
  set loopobj [sct loopobj]
  if {[silent 0 sct geterror] eq "0"} {
    updateval $loopobj $value
  }
  return
}

proc piloop::setinvar {} {
  catch {
    [sct controllerName] killupdatescript [hval [sct]] piloop::updateinput1
  }
  [sct controllerName] updatescript [sct target] piloop::updateinput1
  [sct controllerName] updatescript [sct parent] piloop::updateinput
  hsetprop [sct target] loopobj [sct parent]
  return
}

proc piloop::settarget {target} {
#  clientput "settarget $target == [silent 0 sct @offvalue]"
  if {$target == [silent 0 sct @offvalue]} {
    hset [sct]/mode 0
  } else {
    hset [sct]/mode 1
  }
  sct print "[sct]/set = $target"
  return idle
}

proc piloop::updtarget {} {
  sct print "[sct]/set = [sct target]"
  return idle
}

proc piloop::checkoutput {} {
  eval "[hvali [sct parent]/outvar] [sct target]"
}
