#
# $Log: not supported by cvs2svn $
# Revision 1.6  2001/11/01 23:31:14  borland
# Now ramps faster if requested.
#
# Revision 1.5  1998/02/27 20:30:49  borland
# Now has a loop for waiting with a 1 second cycle. Allows aborting the
# ramp.
#
# Revision 1.4  1997/03/26 21:39:13  borland
# Modified to do exponential functions.  Improved error returns.  Added abort
# function.
#
# Revision 1.3  1996/03/13  01:00:07  borland
# Added better error returns.
#
# Revision 1.2  1996/02/26  15:20:45  borland
# Fixed unescaped square brackets in Help routines.
#
# Revision 1.1  1996/01/16  21:49:10  saunders
# Added application specific tcl procedure libraries, which are installed
# as libraries, not applications.
#
#

proc APScaRampHelp {} {
    return "APScaRamp -pvList <list> -variableList <list> \[-beginList <list>\] -endList <list> -steps <number> -pause <seconds> \[-dryRun 1\] \[-functionList <list>\] \[-abortVariable <string>\]"
}

proc APScaRamp {args} {
    set pvList {}
    set variableList {}
    set beginList {}
    set endList {}
    set steps 0
    set pause 0
    set dryRun 0
    set abortVariable ""
    set functionList {}
    set review 0
    set postStepCallback APSNoOp
    APSStrictParseArguments {pvList variableList beginList endList pause steps dryRun \
                         abortVariable functionList postStepCallback review}
    eval global errorCode $abortVariable
    
    set pvNames [llength $pvList]
    if {$steps<1} {
        return -code error "APScaRamp: -steps less than 1"
    }
    if {!$pvNames} {
        return -code error "APScaRamp: No pvNames"
    }
    if {$pvNames!=[llength $endList] || $pvNames!=[llength $variableList]} {
        return -code error "APScaRamp: endList, pvList, and/or variableList are of different lengths" 
    }
    if {[llength $beginList] && $pvNames!=[llength $beginList]} {
        return -code error "APScaRamp: beginList and pvList are of different lengths"
    }
    if {[llength $functionList] && $pvNames!=[llength $functionList]} {
        return -code error "APScaRamp: functionList and pvList are of different lengths"
    }
    foreach elem $variableList {
        global $elem
    }
    if $dryRun {
        puts stderr "Ramping..."
    }
    if {[pv linkw $variableList $pvList]!=0} {
        return -code error "APScaRamp: $errorCode"
    }
    if ![llength $beginList] {
        set beginList {}
        for {set index 0} {$index<$pvNames} {incr index} {
            set variable [lindex $variableList $index]
            if [pv getw $variable]!=0 {
                return -code error "APScaRamp: Reading [lindex $pvList $index]: $errorCode"
            }
            lappend beginList [subst \$$variable]
        }
    }
    if ![llength $functionList] {
        set functionList [APSReplicateItem -item line -number $pvNames]
    }
    for {set index 0} {$index<$pvNames} {incr index} {
        switch [lindex $functionList $index] {
            decay1 {
                set decayRate 1
            }
            decay2 {
                set decayRate 2
            }
            decay3 {
                set decayRate 3
            }
            decay4 {
                set decayRate 4
            }
            decay5 {
                set decayRate 5
            }
            default {
                set decayRate 0
            }
        }
        if $decayRate {
            set begin [lindex $beginList $index]
            set end   [lindex $endList   $index]
            set decayCoef2 [expr (1.0*$end-$begin)/(exp(-1.0*$decayRate)-1)]
            set decayCoef1 [expr $begin-$decayCoef2]
        } else {
            set decayCoef2 0
            set decayCoef1 0
        }
        lappend decayCoef1List $decayCoef1
        lappend decayCoef2List $decayCoef2
        lappend decayRateList  [expr -(1.0*$decayRate)/($steps-1.0)]
    }
    
    set PI 3.141593
    if $review {
        set tmpRoot /tmp/[APSTmpString]
        for {set index 0} {$index<$pvNames} {incr index} {
            set timeList ""
            set valueList ""
            set pv [lindex $pvList $index]
            for {set step 0} {$step<$steps} {incr step} {
                if $pause==0 {
                    set value [lindex $endList   $index]
                } else {
                    set begin [lindex $beginList $index]
                    set end   [lindex $endList   $index]
                    switch [lindex $functionList $index] {
                        line {
                            set value [expr ((1.0*$end-$begin)/($steps-1))*$step+$begin]
                        }
                        cosin {
                            if {$begin<$end} {
                                set value [expr ($end - $begin)/2.0 * (1 + cos($PI + $PI/($steps-1) * $step)) + $begin]
                            } else {
                                set value [expr ($begin - $end)/2.0 * ( 1 + cos($PI/($steps-1) * $step)) + $end]
                            }
                        }
                        default {
                            set decayCoef1 [lindex $decayCoef1List $index]
                            set decayCoef2 [lindex $decayCoef2List $index]
                            set decayRate  [lindex $decayRateList $index]
                            set value [expr $decayCoef1 + $decayCoef2*exp($decayRate*$step)]
                        }
                    }
                }
                lappend timeList [expr $step * $pause]
                lappend valueList $value
            }
            if [catch {exec sddsmakedataset $tmpRoot.$pv -col=RampTime,type=double,units=s -data=[join $timeList ,] \
                         -col=RampValue,type=double,units=A -data=[join $valueList ,] } result] {
                return -code error "Error creating $tmpRoot.$pv file: $result"
            }
            APSAddToTmpFileList -ID ramppvs -fileList $tmpRoot.$pv
            exec sddsplot -col=RampTime,RampValue $tmpRoot.$pv -topline=$pv &
        }
        return
    }
    for {set step 0} {$step<$steps} {incr step} {
        if {[string length $abortVariable] && [subst \$$abortVariable]} {
            return -code ok 
        }
        for {set index 0} {$index<$pvNames} {incr index} {
            if $pause==0 {
                set value [lindex $endList   $index]
            } else {
                set begin [lindex $beginList $index]
                set end   [lindex $endList   $index]
                if {$step==0} {
                    if [pv getw [lindex $variableList $index]] {
                        return -code error "APScaRamp: reading [lindex $pvList $index]: $errorCode"
                    }
                    set pv [lindex $pvList $index]
                    set val [set [lindex $variableList $index]]
                    if {$val!=0} {
                        if {[expr abs(($begin-$val)/$val)]>0.05} {
                            if ![APSYesNoPopUp "$pv the start value is too far away from its current value, do you still want to continue ramp it?"] {
                                return -code error "The start value of $pv is too far away from its current value, can not start ramp."
                            }
                        }
                    } else {
                        if {[expr abs($begin)]>0.005} {
                            if ![APSYesNoPopUp "$pv the start value is not zero (current value), do you still want to continue ramp it?"] {
                                return -code error "The start value of $pv is too far away from its current value, can not start ramp."
                            }
                        }
                    }
                }
                switch [lindex $functionList $index] {
                    line {
                        set value [expr ((1.0*$end-$begin)/($steps-1))*$step+$begin]
                    }
                    cosin {
                        if {$begin<$end} {
                            set value [expr ($end - $begin)/2.0 * (1 + cos($PI + $PI/($steps-1) * $step)) + $begin]
                        } else {
                            set value [expr ($begin - $end)/2.0 * ( 1 + cos($PI/($steps-1) * $step)) + $end]
                        }
                    }
                    default {
                        set decayCoef1 [lindex $decayCoef1List $index]
                        set decayCoef2 [lindex $decayCoef2List $index]
                        set decayRate  [lindex $decayRateList $index]
                        set value [expr $decayCoef1 + $decayCoef2*exp($decayRate*$step)]
                    }
                }
            }
            if !$dryRun {
                set [lindex $variableList $index] $value
                #puts stderr "$step set [lindex $pvList $index] to $value"
                if [pv putw [lindex $variableList $index]]!=0 {
                    return -code error "APScaRamp: Setting [lindex $pvList $index]: $errorCode"
                }
            } else {
                puts stderr "Pretending to set [lindex $pvList $index] to $value"
            }
            if {[string length $abortVariable] && [subst \$$abortVariable]} {
                return -code ok "Aborted"
            }
        }
        eval $postStepCallback $step $steps
        if $pause==0 break
        set leftToWait [expr int($pause*1000)]
        set interval [expr int($leftToWait/20.0)]
        if $interval>1000 {
            set interval 1000
        }
        while {$leftToWait>0} {
            after $interval
            if [string length [info command update]] {
                update
            }
            if {[string length $abortVariable] && [subst \$$abortVariable]} {
                return -code ok "Aborted"
            }
            incr leftToWait -$interval
        }
    }
    return -code ok 
}

proc APSStandardizeWithDecayHelp {} {
    return "APSStandardizeWithDecay -pv <pv> -lowerLimit <number> -upperLimit <number> -numSteps <integer> -stepInterval <number> -numCycles <integer> -approachFromLowerLimit <0|1> -approachFromUpperLimit <0|1> -finalSetpoint <number>"
}

proc APSStandardizeWithDecay {args} {
    set pv ""
    set lowerLimit 0
    set upperLimit 0
    set numSteps 60
    set stepInterval 1
    set numCycles 1
    set approachFromLowerLimit 0
    set approachFromUpperLimit 0
    set finalSetpoint 0
    set decayRate 5

    APSStrictParseArguments {pv lowerLimit upperLimit numSteps stepInterval numCycles approachFromLowerLimit approachFromUpperLimit finalSetpoint decayRate}

    if {$pv == ""} {
        return -code error "APSStandardizeWithDecay: no PV given"
    }
    if {$lowerLimit >= $upperLimit} {
        return -code error "APSStandardizeWithDecay: invalid upper and lower limits"
    }
    if {$numSteps <= 0} {
        return -code error "APSStandardizeWithDecay: invalid number of steps"
    }
    if {$numCycles < 0} {
        return -code error "APSStandardizeWithDecay: invalid number of cycles"
    }
    if {($finalSetpoint < $lowerLimit) && ($finalSetpoint > $upperLimit)} {
        return -code error "APSStandardizeWithDecay: invalid final setpoint"
    }
    if {$approachFromLowerLimit && $approachFromUpperLimit} {
        return -code error "APSStandardizeWithDecay: cannot approach final value from both upper and lower limits"
    }
    
    set pvlink "pv[clock clicks]"
    global $pvlink errorCode

    set errorCode ""
    if {[pv linkw $pvlink $pv]!=0} {
        return -code error "APSStandardizeWithDecay: problem connecting to pv $pv"
    }
    set decayCoef2UP [expr (1.0 * $upperLimit - $lowerLimit)/(exp(-1.0 * $decayRate)-1)]
    set decayCoef1UP [expr $lowerLimit - $decayCoef2UP]
    set decayCoef2DOWN [expr (1.0 * $lowerLimit - $upperLimit)/(exp(-1.0 * $decayRate)-1)]
    set decayCoef1DOWN [expr $upperLimit - $decayCoef2DOWN]
    set initDecayRate $decayRate
    set decayRate [expr -(1.0*$initDecayRate)/($numSteps-1.0)]
    if {$numCycles > 0} {
        #Bring the current to lowerLimit prior to starting the first cycle
        if {[set $pvlink] > $lowerLimit} {
            if {[expr [set $pvlink] - $lowerLimit] < 1e-12} {
                #Close enough
            } else {
                for {set step 0} {$step < $numSteps} {incr step} {
                    set setpoint [expr $decayCoef1DOWN + $decayCoef2DOWN*exp($decayRate*$step)]
                    if {$setpoint < $lowerLimit} {
                        set setpoint $lowerLimit
                    }
                    if {$setpoint < [set $pvlink]} {
                        set $pvlink $setpoint
                        if [pv putw $pvlink]!=0 {
                            return -code error "APSStandardizeWithDecay: problem setting $pv"
                        }
                        APSWaitWithUpdate -waitSeconds $stepInterval
                    }
                }
            }
        }
        for {set i 0} {$i < $numCycles} {incr i} {
            #Bring the current from lowerLimit to highLimit
            for {set step 0} {$step < $numSteps} {incr step} {
                set setpoint [expr $decayCoef1UP + $decayCoef2UP*exp($decayRate*$step)]
                if {$setpoint > $upperLimit} {
                    set setpoint $upperLimit
                }
                set $pvlink $setpoint
                if [pv putw $pvlink]!=0 {
                    return -code error "APSStandardizeWithDecay: problem setting $pv"
                }
                APSWaitWithUpdate -waitSeconds $stepInterval
            }
            #Bring the current from highLimit to lowerLimit
            for {set step 0} {$step < $numSteps} {incr step} {
                set setpoint [expr $decayCoef1DOWN + $decayCoef2DOWN*exp($decayRate*$step)]
                if {$setpoint < $lowerLimit} {
                    set setpoint $lowerLimit
                }
                set $pvlink $setpoint
                if [pv putw $pvlink]!=0 {
                    return -code error "APSStandardizeWithDecay: problem setting $pv"
                }
                APSWaitWithUpdate -waitSeconds $stepInterval
            }
        }
    }
    if {$approachFromUpperLimit} {
        #Bring the current up to highLimit
        if {[set $pvlink] < $upperLimit} {
            if {[expr $upperLimit - [set $pvlink]] < 1e-12} {
                #Close enough
            } else {
                for {set step 0} {$step < $numSteps} {incr step} {
                    set setpoint [expr $decayCoef1UP + $decayCoef2UP*exp($decayRate*$step)]
                    if {$setpoint > $upperLimit} {
                        set setpoint $upperLimit
                    }
                    if {$setpoint > [set $pvlink]} {
                        set $pvlink $setpoint
                        if [pv putw $pvlink]!=0 {
                            return -code error "APSStandardizeWithDecay: problem setting $pv"
                        }
                        APSWaitWithUpdate -waitSeconds $stepInterval
                    }
                }
            }
        }
    }
    if {$approachFromLowerLimit} {
        #Bring the current down to lowerLimit.
        if {[set $pvlink] > $lowerLimit} {
            if {[expr [set $pvlink] - $lowerLimit] < 1e-12} {
                #Close enough
            } else {
                for {set step 0} {$step < $numSteps} {incr step} {
                    set setpoint [expr $decayCoef1DOWN + $decayCoef2DOWN*exp($decayRate*$step)]
                    if {$setpoint < $lowerLimit} {
                        set setpoint $lowerLimit
                    }
                    if {$setpoint < [set $pvlink]} {
                        set $pvlink $setpoint
                        if [pv putw $pvlink]!=0 {
                            return -code error "APSStandardizeWithDecay: problem setting $pv"
                        }
                        APSWaitWithUpdate -waitSeconds $stepInterval
                    }
                }
            }
        }
    }
    if {[set $pvlink] < $finalSetpoint} {
        if {[expr $finalSetpoint - [set $pvlink]] < 1e-12} {
            #Close enough
        } else {
            #Bring the current up to the final setpoint
            set decayRate $initDecayRate
            set decayCoef2UP [expr (1.0 * $finalSetpoint - [set $pvlink])/(exp(-1.0 * $decayRate)-1)]
            set decayCoef1UP [expr [set $pvlink] - $decayCoef2UP]
            set decayRate [expr -(1.0*$initDecayRate)/($numSteps-1.0)]
            for {set step 0} {$step < $numSteps} {incr step} {
                set setpoint [expr $decayCoef1UP + $decayCoef2UP*exp($decayRate*$step)]
                if {$setpoint > $upperLimit} {
                    set setpoint $upperLimit
                }
                set $pvlink $setpoint
                if [pv putw $pvlink]!=0 {
                    return -code error "APSStandardizeWithDecay: problem setting $pv"
                }
                APSWaitWithUpdate -waitSeconds $stepInterval
            }
        }
    } elseif {[set $pvlink] > $finalSetpoint} {
        if {[expr [set $pvlink] - $finalSetpoint] < 1e-12} {
            #Close enough
        } else {
            #Bring the current down to the final setpoint
            set decayRate $initDecayRate
            set decayCoef2DOWN [expr (1.0 * $finalSetpoint - [set $pvlink])/(exp(-1.0 * $decayRate)-1)]
            set decayCoef1DOWN [expr [set $pvlink] - $decayCoef2DOWN]
            set decayRate [expr -(1.0*$initDecayRate)/($numSteps-1.0)]
            for {set step 0} {$step < $numSteps} {incr step} {
                set setpoint [expr $decayCoef1DOWN + $decayCoef2DOWN*exp($decayRate*$step)]
                if {$setpoint < $lowerLimit} {
                    set setpoint $lowerLimit
                }
                set $pvlink $setpoint
                if [pv putw $pvlink]!=0 {
                    return -code error "APSStandardizeWithDecay: problem setting $pv"
                }
                APSWaitWithUpdate -waitSeconds $stepInterval
            }
        }
    }
    set $pvlink $finalSetpoint
    if [pv putw $pvlink]!=0 {
        return -code error "APSStandardizeWithDecay: problem setting $pv"
    }
}
