#!/bin/sh
# \
  exec oagwish "$0" "$@"

set auto_path [linsert $auto_path 0 /usr/local/oag/apps/lib/$env(HOST_ARCH)]
set auto_path [linsert $auto_path 0 /usr/local/oag/lib_patch/$env(HOST_ARCH)]
set CVSRevisionAuthor "\$Revision: 1.55 $ \$Author: soliday $"

proc getAverageReading {number delay} {
    global tclVarList gunChoice
    eval global $tclVarList
    set lcv 0
    set sum 0
    while {$lcv < $number} {
        pv getw ${gunChoice}currMon
        set sum [expr $sum + [set ${gunChoice}currMon]]
        incr lcv
        set waitDone 0
        after $delay "set waitDone 1"
        tkwait variable waitDone
    }
    if {$number > 0} {
        set sum [expr $sum / $number]
    }
    return $sum
}

proc tweakHeater {amount tweaktype} {
    global tclVarList absolute relative gunChoice
    eval global $tclVarList
    if {$tweaktype == $relative} {
        pv getw ${gunChoice}heaterSetpoint
        set ${gunChoice}heaterSetpoint [expr [set ${gunChoice}heaterSetpoint] + $amount]
        pv putw ${gunChoice}heaterSetpoint
    } elseif {$tweaktype == $absolute} {
        set ${gunChoice}heaterSetpoint $amount
        pv putw ${gunChoice}heaterSetpoint
    } else {
        puts stdout "Error in heater setting type!"
        exit
    }
}

proc switchApplicationState {run} {
    if $run {
        .userFrame.frame.sampleReps.entry config -state disabled
        .userFrame.frame.sampleDelay.entry config -state disabled
        .userFrame.frame.gunChoice.frame.button1 configure -state disabled
        catch {.userFrame.frame.gunChoice.frame.button2 configure -state disabled}
        APSEnableButton .userFrame.frame.stop.button
        APSEnableButton .userFrame.frame.stop2.button
        APSDisableButton .userFrame.frame.run.button
    } else {
        .userFrame.frame.sampleReps.entry config -state normal
        .userFrame.frame.sampleDelay.entry config -state normal
        .userFrame.frame.gunChoice.frame.button1 configure -state normal
        catch {.userFrame.frame.gunChoice.frame.button2 configure -state normal}
        APSDisableButton .userFrame.frame.stop.button
        APSDisableButton .userFrame.frame.stop2.button
        APSEnableButton .userFrame.frame.run.button
    }
}

proc ResetEntries {} {
    global gunChoice ICmean ITScurrTarget RG1currTarget RG2currTarget tweek1 tweek2 exitHeaterCurrent
    pv getw ${gunChoice}currTarget
    set ICmean [set ${gunChoice}currTarget]
    if {$ICmean < 0.01} {
        set ICmean 0.01
        set ${gunChoice}currTarget 0
        pv putw ${gunChoice}currTarget
    }
    if {$ICmean > 1.0} {
        set ICmean 1.0
        set ${gunChoice}currTarget 1.0
        pv putw ${gunChoice}currTarget
    }
    switch $gunChoice {
        ITS {
            set tweek1 0.02
            set tweek2 0.06
            set exitHeaterCurrent 6.0
        }
        RG1 {
            set tweek1 0.002
            set tweek2 0.006
            set exitHeaterCurrent 6.9
        }
        RG2 {
            set tweek1 0.002
            set tweek2 0.006
            set exitHeaterCurrent 6.0
        }
    }

}

proc stabilizeCurrent {} {

    global tclVarList running buttonPress useRC llrfPresent \
      absolute relative reps delay exitHeaterCurrent ICmean \
      gunChoice ITScurrTarget RG1currTarget RG2currTarget ITSmaxHeaterPower RG1maxHeaterPower RG2maxHeaterPower \
      MinL1SectorFwdPower MinL1SectorFwdPower2 tweek1 tweek2 minIC
    eval global $tclVarList

    if [catch {exec cavget -list=L1:RFG:RF:SW2:positionMI} gunChoiceTest] {
        APSSetVarAndUpdate mainStatus "Unable to read RF Switch #2"
        return
    }
    if {$gunChoice != "ITS"} {
        if {$gunChoice != $gunChoiceTest} {
            set gunChoice $gunChoiceTest
            APSSetVarAndUpdate mainStatus "Switching guns"
        }
    }
    # variables needed locally for current stabilization
    set absolute 0
    set relative 1
    # this defines the target value to try to hold
    set delta 100

    set restart 0
    if $useRC {
        catch {APSRunControlExit}
        if {$gunChoice == "ITS"} {
            set RCPV LTS:RFGunBeamIcontrollerRC
            set rcDesc "ITS Curr Stab."
        } else {
            set RCPV L:RFGunBeamIcontrollerRC
            set rcDesc "RFG Curr Stab."
        }

        if {[catch {APSRunControlInit -pv $RCPV \
                      -description $rcDesc \
                      -timeout 60000} result]} {
            APSSetVarAndUpdate mainStatus "Another RF Gun Current Stabilizer is apparently running."
            APSSetVarAndUpdate mainStatus "Check the process control screen for machine name and process id."
            APSSetVarAndUpdate mainStatus "|----------------------|"
            APSSetVarAndUpdate mainStatus "Unable to get runControl access: $result"
            return
        } 
    }

    set semaphore "holdCurrent starting"
    pv putw semaphore
    APSSetVarAndUpdate mainStatus $semaphore
    
    APSSetVarAndUpdate mainStatus "Acquiring initial data points..."
    
    
    # load in the five data points, at 1-second intervals
    set lcv 1
    while {$lcv <= 5} {
        set IC($lcv) [getAverageReading $reps $delay]
        APSSetVarAndUpdate mainStatus "shot $lcv read"
        incr lcv
        if {$useRC && [catch {APSRunControlPing} result]} {
            APSSetVarAndUpdate mainStatus "Ending via ping from runControl"
            return
        }
    }
    
    # next part loops around continuously, checking the integrated current and
    # tweaking the cathode filament current if it drifts too far in one direction
    # or another.
    set lcv 1
    pv getw $tclVarList

    # This will loop until a Process 15 is put into the semaphore status
    set llrfCounter 10
    set fwdPwrCounter 10
    set warningBeep 0
    while {$running == 1} {
        pv getw $tclVarList
        set timeStart [clock seconds]
        if {$useRC && [catch {APSRunControlPing} result]} {
            APSSetVarAndUpdate mainStatus "Ending via ping from runControl"
            bell
            break
        }
        if {[expr [clock seconds] - $timeStart] > 10} {
            set restart 1
            break
        } elseif {$useRC} {
            if {[catch {APScavget -list=${RCPV}.ALRM -num} results]} {
            } else {
                if {$results != "0"} {
                    catch {APScavput -list=${RCPV}.ALRM=0}
                }
            }
        }

        if {($L1SectorFwdPower < $MinL1SectorFwdPower) && ($L1SectorFwdPower > $MinL1SectorFwdPower2)} {
            incr fwdPwrCounter -1
            if {!$fwdPwrCounter} {
                if {$gunChoice == "ITS"} {
                    APSSetVarAndUpdate mainStatus "Ending due to insufficient L3 forward power"
                    #set PFN [exec cavget -list=L3:MO:PFNvAI]
                } else {
                    APSSetVarAndUpdate mainStatus "Ending due to insufficient L1 forward power"
                    set PFN [exec cavget -list=L1:MO:PFNvAI]
                    if {$PFN > 15} {
                        set running -1
                    }
                }
                break
            }
            if {$gunChoice == "ITS"} {
                APSSetVarAndUpdate mainStatus "Warning: insufficient L3 forward power detected"
            } else {
                APSSetVarAndUpdate mainStatus "Warning: insufficient L1 forward power detected"
            }
            if {$warningBeep > 1} {
                bell
            }
            incr warningBeep
            after 1000 
            continue
        } else {
            set fwdPwrCounter 10
            set warningBeep 0
        }
        if {($L1SectorFwdPower < $MinL1SectorFwdPower) && ($L1SectorFwdPower < $MinL1SectorFwdPower2)} {
            if {$gunChoice == "ITS"} {
                APSSetVarAndUpdate mainStatus "Low L3 forward power detected: No adjustments made"
            } else {
                APSSetVarAndUpdate mainStatus "Low L1 forward power detected: No adjustments made"
            }
            after 1000 
            continue
        }
        #        if {($DG3TriggerReady != 1)} {
        #            APSSetVarAndUpdate mainStatus "DG3 Trigger not ready: No adjustments made"
        #            after 1000
        #            continue
        #        }
        if {($IOCTrig2Enable != "Enabled")} {
            APSSetVarAndUpdate mainStatus "IOCTrig2Enable not ready: No adjustments made"
            after 1000
            continue
        }
        set IC($lcv) [getAverageReading $reps $delay]
        # find the current average from the last 5 shots
        set ICrunavg [expr abs($IC(1) + $IC(2) + $IC(3) + $IC(4) + $IC(5)) * .2]

        if {$gunChoice == "ITS"} {
            if {$ICrunavg < $minIC} {
                APSSetVarAndUpdate mainStatus "LTS:CM1:measCurrentCM too low: No adjustments made"
                after 1000
                continue
            }
        }

        # determine the percent deviation from the initial mean, and the slope
        # of the change

        set olddelta $delta
        set delta [expr round($ICrunavg / $ICmean * 100)]
        set slope [expr $delta - $olddelta]
        APSSetVarAndUpdate mainStatus "start=$ICmean   curr=[format "%.4f" $ICrunavg]   delta=$delta slope=$slope"
        # write a status line to the scope semaphore display
        set semaphore "Curr $delta %"
        pv putw semaphore
        # determine what action, if any, is needed based on the delta
        if {$delta > 103} {
            APSSetVarAndUpdate mainStatus "Current too high"
            if {$slope > 0} {
                APSSetVarAndUpdate mainStatus "slope is positive ... correcting."
                tweakHeater -$tweek2 $relative
            } elseif {$slope < 0} {
                APSSetVarAndUpdate mainStatus "slope is negative ... allowing to drift down."
            } else {
                APSSetVarAndUpdate mainStatus "current not changing ... nudging down."
                tweakHeater -$tweek1 $relative
            } 
        } elseif {$delta < 97} {
            APSSetVarAndUpdate mainStatus "Current too low"
            if {($delta < 92) && 
                ([set ${gunChoice}heaterPower] > [set ${gunChoice}maxHeaterPower])} {
                APSSetVarAndUpdate mainStatus "Heater power is at limit ... allowing current to drift up."
            } elseif {$slope < 0} {
                APSSetVarAndUpdate mainStatus "slope is negative ... correcting."
                tweakHeater $tweek2 $relative
            } elseif {$slope > 0} {
                APSSetVarAndUpdate mainStatus "slope is positive ... allowing to drift up."
            } else {
                APSSetVarAndUpdate mainStatus "current not changing ... nudging up."
                tweakHeater $tweek1 $relative
            }
        } else { 
            APSSetVarAndUpdate mainStatus "current in normal range."
            if {$slope > 0 && $delta > 101} {
                APSSetVarAndUpdate mainStatus "current drifting up ... nudging down to center."
                tweakHeater -$tweek1 $relative
            } elseif {$slope < 0 && $delta < 99} {
                APSSetVarAndUpdate mainStatus "current drifting down ... nudging up to center."
                tweakHeater $tweek1 $relative
            }
        }
        
        
        # increment the index counter, roll it back to 1 if needed
        incr lcv
        if {$lcv > 5} {set lcv 1}

        pv getw ${gunChoice}currTarget
        set ICmean [set ${gunChoice}currTarget]
        if {$ICmean < 0.01} {
            set ICmean 0.01
            set ${gunChoice}currTarget 0
            pv putw ${gunChoice}currTarget
        }
        if {$ICmean > 1.0} {
            set ICmean 1.0
            set ${gunChoice}currTarget 1.0
            pv putw ${gunChoice}currTarget
        }

        # do an idiot check for the exit heater current
        if {$exitHeaterCurrent<0.0} {set exitHeaterCurrent 0.0}
        if {$gunChoice == "RG1"} {
            if {$exitHeaterCurrent > 7} {set exitHeaterCurrent 6.9}
        } elseif {$gunChoice == "RG2"} {
            if {$exitHeaterCurrent > 8.5} {set exitHeaterCurrent 8.0}
        } else {
            if {$exitHeaterCurrent > 7} {set exitHeaterCurrent 6.0}
        }
    }
    
    if {$restart} {
        after 1000 {
            .userFrame.frame.run.button flash
            .userFrame.frame.run.button invoke
        }
    } else {
        # and that should be the end of the control loop.
        if $useRC {
            catch {APSRunControlExit}
        }
        APSSetVarAndUpdate mainStatus "Ending current stabilization"
        set semaphore "Curr. stab ending" ; pv putw semaphore
        if {$running != -1} {
            tweakHeater $exitHeaterCurrent $absolute
        }
    }
}

APSApplication . \
  -name "RF Gun Current Stabilizer" \
  -version $CVSRevisionAuthor \
  -overview "This application stabilizes the beam current emitted by the thermionic-cathode rf guns"


# set up for run control

set start ""
set args $argv
if [catch {exec cavget -list=L1:RFG:RF:SW2:positionMI} gunChoice] {
    set gunChoice RG2
    APSSetVarAndUpdate mainStatus "Unable to read RF Switch #2"
}
if {($gunChoice != "RG1") && ($gunChoice != "RG2")} {
    set gunChoice RG2
    APSSetVarAndUpdate mainStatus "RF Switch \#2 is not at RG1 or RG2"
}

APSParseArguments {start gunChoice}

set useRC 1

# define the standard PV/variable parallel list definition procedure
proc addToLists {args} {
    global pvList tclVarList
    APSAddToParallelLists -listNames {pvList tclVarList} \
      -listItems $args
}

if {$gunChoice == "ITS"} {
    addToLists LTS:CM1:measCurrentCM ITScurrMon
    addToLists LTS:CM1:beamCurrentAO ITScurrTarget
    addToLists LTS:G:VoltageAO ITSheaterSetpoint
    addToLists LTS:G:PowerCC ITSheaterPower
    addToLists LTS:PT3:DC1ARF L1SectorFwdPower
} else {
    addToLists L1:RG1:CM1:measCurrentCC RG1currMon
    addToLists L1:RG2:CM1:measCurrentCC RG2currMon
    addToLists L1:RG1:beamCurrentAO RG1currTarget
    addToLists L1:RG2:beamCurrentAO RG2currTarget
    addToLists L1:RG1:HTR:VoltageAO RG1heaterSetpoint
    addToLists L1:RG2:HTR:VoltageAO RG2heaterSetpoint
    addToLists L1:RG1:HTR:PowerCC RG1heaterPower
    addToLists L1:RG2:HTR:PowerCC RG2heaterPower
    addToLists L1:SE:DC1ARF.VAL L1SectorFwdPower
}
addToLists L1:SCOPE1:scopeInUseMO.DESC semaphore
addToLists L1:LL:readyCC llrfPresent
#addToLists LI:TM:DG3:readyCC DG3TriggerReady
addToLists L1:RG:IOCTrig2EnableC IOCTrig2Enable

if {$gunChoice == "ITS"} {
    set ITSmaxHeaterPower [expr [exec cavget -list=LTS:G:PowerCC.HIHI] - 1.0]
    set MinL1SectorFwdPower 1.5e6
    set MinL1SectorFwdPower2 3e4
    set minIC .01
} else {
    set RG1maxHeaterPower [expr [exec cavget -list=L1:RG1:HTR:PowerCC.HIHI] - 1.0]
    set RG2maxHeaterPower [expr [exec cavget -list=L1:RG2:HTR:PowerCC.HIHI] - 1.0]
    set MinL1SectorFwdPower 4e6
    set MinL1SectorFwdPower2 1e5
}

set tweek1 0.0005
set tweek2 0.002

set mainStatus ""
set running 0
set reps 20
set delay 200
set exitHeaterCurrent 6.0
set ICmean 0

pack [frame .userFrame.frame] -fill x -expand false -side bottom
APSScrolledStatus .record \
  -parent .userFrame \
  -textVariable mainStatus \
  -width 80 \
  -height 15 \
  -lineLimit 500 \
  -packOption "-fill both -expand true" -withButtons 1
pack configure .userFrame.record.frame -fill both -expand true

APSLabeledEntry .sampleReps \
  -parent .userFrame.frame \
  -label "Current monitor samples" \
  -textVariable reps \
  -contextHelp "Number of sample points to take per acquisition" \
  -type integer
APSLabeledEntry .sampleDelay \
  -parent .userFrame.frame \
  -label "Delay between samples (ms)" \
  -textVariable delay \
  -contextHelp "Delay in milliseconds between data point acquisitions" \
  -type integer
APSLabeledEntry .finalHeater \
  -parent .userFrame.frame \
  -label "Heater setpoint at exit (V)" \
  -textVariable exitHeaterCurrent \
  -contextHelp "The rf gun cathode heater will be set to this value when current stabilization is stopped" \
  -type real
APSLabeledOutput .icmean \
  -parent .userFrame.frame \
  -label "Beam current setpoint" \
  -textVariable ICmean \
  -contextHelp "This is the setpoint for the current control"
if {$gunChoice == "ITS"} {
    APSLabeledEntry .minIC \
      -parent .userFrame.frame \
      -label "Minimum CM1 Current" \
      -textVariable minIC \
      -contextHelp "CM1 current must exceed this value, or this script will not change the heater power." -type real
    APSLabeledEntry .minL1Pwr \
      -parent .userFrame.frame \
      -label "Minimum L3 Sector Forward Power" \
      -textVariable MinL1SectorFwdPower \
      -contextHelp "L3 sector forward power must exceed this value, or this script will not change the heater power." -type real
    APSLabeledEntry .tweek2 \
      -parent .userFrame.frame \
      -label "Heater gain if the current is more than 3% out of range" \
      -textVariable tweek2 \
      -contextHelp "Amount to adjust heater setpoint if the current is more than 3% out of range."
    APSLabeledEntry .tweek1 \
      -parent .userFrame.frame \
      -label "Heater gain if the current is more than 1% out of range" \
      -textVariable tweek1 \
      -contextHelp "Amount to adjust heater setpoint if the current is more than 3% out of range."
} else {
    APSLabeledEntry .minL1Pwr \
      -parent .userFrame.frame \
      -label "Minimum L1 Sector Forward Power" \
      -textVariable MinL1SectorFwdPower \
      -contextHelp "L1 sector forward power must exceed this value, or this script will not change the heater power." -type real
}
APSButton .run \
  -parent .userFrame.frame \
  -text "Run" \
  -command { switchApplicationState 1 ; set running 1 ; stabilizeCurrent ; switchApplicationState 0 }
APSButton .stop \
  -parent .userFrame.frame \
  -text "Stop" \
  -command { switchApplicationState 0 ; set running -1 ; bell ; update }
APSButton .stop2 \
  -parent .userFrame.frame \
  -text "Stop and Lower Heater Set Point" \
  -command { switchApplicationState 0 ; set running 0 ; bell ; update }
APSDisableButton .userFrame.frame.stop.button
APSDisableButton .userFrame.frame.stop2.button
APSButton .info \
  -parent .userFrame.frame \
  -text "Info" \
  -command { 
      if $useRC {
          if {$gunChoice == "ITS"} {
              set RCPV LTS:RFGunBeamIcontrollerRC
          } else {
              set RCPV L:RFGunBeamIcontrollerRC
          }
          exec medm -x -attach -macro RCPV=$RCPV \
            ./sr/psApp/APSRunControlSingle.adl &
      }
  }
if {$gunChoice == "ITS"} {
    APSRadioButtonFrame .gunChoice \
      -parent .userFrame.frame \
      -label "   Gun Choice:" \
      -variable gunChoice \
      -buttonList "ITS" \
      -valueList "ITS" \
      -orientation horizontal \
      -commandList {ResetEntries}
} else {
    APSRadioButtonFrame .gunChoice \
      -parent .userFrame.frame \
      -label "   Gun Choice:" \
      -variable gunChoice \
      -buttonList "RG1 RG2" \
      -valueList "RG1 RG2" \
      -orientation horizontal \
      -commandList {ResetEntries ResetEntries}
}
foreach t $tclVarList p $pvList {
    if [pv linkw "$t" "$p"] {
        update
        APSAlertBox [APSUniqueName .] -errorMessage "CA problem: Can't connect to $p" 
        exit 1
    }
}

proc ExitNicely {} {
    global useRC
    .userFrame.frame.stop.button flash
    .userFrame.frame.stop.button invoke
    if $useRC {
        catch {APSRunControlExit}
    }
}

proc Restart {cid addr port} {
    if {$addr != "127.0.0.1"} {
        close $cid
        return
    }
    close $cid
    .userFrame.frame.stop.button flash
    .userFrame.frame.stop.button invoke
    
    global gunChoice
    if {$gunChoice != "ITS"} {
        if [catch {exec cavget -list=L1:RFG:RF:SW2:positionMI} gunChoice] {
            set gunChoice RG2
            APSSetVarAndUpdate mainStatus "Unable to read RF Switch \#2"
        }
        if {($gunChoice != "RG1") && ($gunChoice != "RG2")} {
            set gunChoice RG2
            APSSetVarAndUpdate mainStatus "RF Switch \#2 is not at RG1 or RG2"
        }
    }
    ResetEntries
    after 3000 {
        .userFrame.frame.run.button flash
        .userFrame.frame.run.button invoke
    }
}

ResetEntries
after 200
if {$gunChoice == "ITS"} {
    #port 4592 is used to tell the fixedCurrent to attempt to restart
    if [catch {socket -server Restart 4592}] {
        APSSetVarAndUpdate mainStatus  "There appears to be another copy of this program already started"
    }
} else {
    #port 4579 is used to tell the fixedCurrent to attempt to restart
    if [catch {socket -server Restart 4579}] {
        APSSetVarAndUpdate mainStatus  "There appears to be another copy of this program already started"
    }
}

.menu.file.menu entryconfigure 1 -command "exec $argv0 &; exit"
dp_atexit append ExitNicely

if {$start == "1"} {
    .userFrame.frame.run.button invoke
}

