namespace eval cryotel {} {
}

proc stdConfig::cryotel {} {  
#  controler uses std with send terminator "\r"
#  5sec comm.interval, receive terminator "\n"
#  and multi line replies will be separated by ","
  controller std "\r" 5 "\n" ","
# the {2} in addition to the command defines the number of expected return values
  prop startcmd "SERIAL{2}"

# driver for Sunpower stirling cooler CryoTel  
# serial interface is set to:
# Char Size/Stop Bits:         8/1    Input Speed:                4800
# Flow Ctrl:                  None    Output Speed:               4800
# Parity:                     None    Modem Control:               None


  obj cryo rd 0.5
  prop label Temperature
  prop update cryotel::updatemean
  prop readcmd "TC{2}"  
  prop readfmt "TC ,%f"

  kids cryotel {

    node cool wr
    prop label Cool
    prop enum on,off
    prop writecmd "SET SSTOP=%i"
    prop readcmd "SET SSTOP{2}"
    prop readfmt "SET SSTOP ,%i"

    node control wr
    default 1
    prop check cryotel::checkcontrol
    prop enum auto_power=1,manual_power=0,controlled_T=2
    prop help "recommended mode: auto_power, use coolpower or holdpower depending on T"
    prop update cryotel::updatecontrol
    prop writecmd "SET PID=%.2f"
    prop readcmd "SET PID{2}"
    prop readfmt "SET PID ,%f"

    node set wr
    prop label Setpoint
    prop check cryotel::checkset
    prop writecmd "SET TTARGET=%.2f"
    prop readcmd "SET TTARGET{2}"
    prop readfmt "SET TTARGET ,%f"

    node setpower wr
    prop check cryotel::checksetpower
    prop writecmd "SET PWOUT=%.2f"
    prop readcmd "SET PWOUT{2}"
    prop readfmt "SET PWOUT ,%f"

    node prop wr
    prop writecmd "SET KP=%.2f"
    prop readcmd "SET KP{2}"
    prop readfmt "SET KP ,%f"

    node integ wr
    prop writecmd "SET KI=%.2f"
    prop readcmd "SET KI{2}"
    prop readfmt "SET KI ,%f"

    node coolpower par 240

    node holdpower par 120   

    node cool_threshold par 100
    prop help "switch to coolpower above this value"

    node hold_threshold par 95
    prop help "switch to holdpower below this value"

    node power rd
    prop label Power
    prop readcmd "P{2}"
    prop readfmt "P ,%f"

    node filter -none
    
    kids filter {
      node period par 180
      prop help "oszillation period / sec"

      node amplitude par 3
      prop help "oszillation amplitude / K (+/-)"

      node precision par 0.1
      
      node raw upd
    
      node intdif upd
    }
  }
}

proc cryotel::updatemean {} {
  set value [stdSct::scanresult]
  set cnt [silent -1 sct cnt]
  if {$cnt < 0} {
    sct update $value
    updateval [sct]/filter/raw $value
    sct cnt 0
    return idle
  }
  incr cnt
  sct sum [expr [silent 0 sct sum] + $value]
  if {$cnt < 10} {
    sct cnt $cnt
  } else {
    set raw [expr [sct sum] / $cnt]
    set filtered [hvali [sct]]
    sct cnt 0
    sct sum 0
    updateval [sct]/filter/raw $raw

    # intdif is accumulating the difference raw - filtered
    # filtered is corrected by +precision or -precision if abs(intdif) is over lim
    set per [hval [sct]/filter/period]
    if {$per == 0} {
      set filtered $raw
    } else {
      set lim [expr 0.5 * [hval [sct]/filter/amplitude]]
      set prec [hval [sct]/filter/precision]
      # 20 = 4 * pollperiod * cnt
      set damp [expr exp(-20 * $prec / $lim / $per)]
      # 10 = cnt * pollperiod * 2
      set intdif [expr ([hvali [sct]/filter/intdif] + ($raw - $filtered) * 10 / $per) * $damp]
      if {$intdif > $lim} {
        set filtered [expr (round(($filtered + $intdif - $lim)/ $prec + 1)) * $prec]
        set intdif [expr $lim - $prec * 2]
      } elseif {$intdif < -$lim} {
        set filtered [expr (round(($filtered + $intdif + $lim)/ $prec - 1)) * $prec]
        set intdif [expr -$lim + $prec * 2]
      }
      updateval [sct]/filter/intdif $intdif
    }
    sct update [format %.5g $filtered]
    if {[hval [sct]/control] == 1} {
      if {$filtered > [hval [sct]/cool_threshold] - 0.01} {
        hset [sct]/setpower [hval [sct]/coolpower]
        if {[hval [sct]/cool]} {
          # switch ON (STOP=0)
          hset [sct]/cool 0
        }
      } elseif {$filtered < [hval [sct]/hold_threshold] + 0.01} {
        set hp [hval [sct]/holdpower]
        if {$hp == 0} {
          if {[hval [sct]/cool] == 0} {
            # switch OFF (STOP=1)
            hset [sct]/cool 1
          }
        } else {
          if {[hval [sct]/cool]} {
            # switch ON (STOP=0)
            hset [sct]/cool 0
          }
          hset [sct]/setpower $hp
        }
      }
    }
  }
  return idle
}

proc cryotel::checkset {} {
  if {[hvali [sct parent]/control] == 0} {
    sct print "setpoint is effective only in controlled_T mode"
  }
}

proc cryotel::checksetpower {} {
  if {[hvali [sct parent]/control] == 2} {
    sct print "setpower is effective only in manual_power mode"
  }
  if {[sct target] == [hvali [sct]]} {
    hsetprop [sct] write stdSct::complete
  } else {
    hsetprop [sct] write stdSct::write
  }
}

proc cryotel::visibility {mode} {
  set showlist {
    0 setpower
    1 {coolpower holdpower cool_threshold hold_threshold}
    2 set
  }
  foreach {m vars} $showlist {
    if {$m == $mode} {
      set visible true
    } else {
      set visible false
    }
    foreach var $vars {
      hsetprop [sct parent]/$var visible $visible
    }
  }
}

proc cryotel::updatecontrol {} {
  set value [stdSct::scanresult]
  if {$value == 0 && [sctval [sct]] == 1} {
    set value 1
  }
  sct update $value
  visibility $value
  return idle
}

proc cryotel::checkcontrol {} {
  visibility [sct target]
  if {[sct target] == 1} {
    hsetprop [sct] write stdSct::completeUpdate
  } else {
    hsetprop [sct] write stdSct::write
  }
}
