#!/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)]
APSDebugPath

APSStandardSetup

set CVSRevisionAuthor "\$Revision: 1.40 $ \$Author: soliday $"

set srGroup "borland emery evans asdops sr"
set psGroup "psgroup carwar tfors"

set systemsImplemented {sr psts}
set optionsImplemented {{""} {"" normal fourcorr}}

set usage "usage: APSCalibrateDCPS -system \{ $systemsImplemented \} \[-option \{ $optionsImplemented \}\]"
set args $argv
set system ""
set option ""

APSParseArguments {system option}
set systemIndex [lsearch -exact $systemsImplemented $system]

if {($systemIndex ==-1) || \
      ([lsearch -exact [lindex $optionsImplemented $systemIndex] $option] ==-1)} {
    APSAlertBox [APSUniqueName .] -errorMessage "$usage"
    exit 1
}

set PSToCalibrate ""
set apsDCPSCalMainDir /home/helios/oagData/DCPSCalibration
set showRaw 0

# System-Specific Initializations
switch $system {
    sr {
        set systemDescription "storage ring"
        set optionDescription ""
        set allowedUsers "$psGroup $srGroup"
        set allowedSubnets "164.54.1 164.54.2 164.54.3"
        set testStandDir "psts"
        set ETSRootList { PS-SR-CORR- PS-SR-SKEW- PS-SR-TRIM- PS-SR-QUAD- PS-SR-SEXT- }
    }
    psts {
        set systemDescription "power supply test stand"
        set allowedUsers "$psGroup"
        set allowedSubnets "164.54.84"
        set ETSRootList { PS-SR-CORR- PS-SR-SKEW- PS-SR-TRIM- PS-SR-QUAD- PS-SR-SEXT- }
        set DRVHList { 150  10  60 460 250 }
        set DRVLList {-150 -10 -60   0   0 }
        if {$option == ""} {
            set option normal
        }
        switch $option {
            normal {
                set optionDescription " (normal slot configuration)"
                set pvRootList { PS:S:C1 PS:S:SQ2 PS:S:S3 PS:S:Q4 PS:S:C5 PS:S:MT6 PS:S:S7 PS:S:Q8 }
                set slot1List { PS-SR-CORR- }
                set slot2List { PS-SR-SKEW- }
                set slot3List { PS-SR-SEXT- }
                set slot4List { PS-SR-QUAD- }
                set slot5List { PS-SR-CORR- }
                set slot6List { PS-SR-TRIM- }
                set slot7List { PS-SR-SEXT- }
                set slot8List { PS-SR-QUAD- }
            }
            fourcorr {
                set optionDescription " (four corrector slots)"
                set pvRootList { PS:S:C1 PS:S:C2 PS:S:S3 PS:S:Q4 PS:S:C5 PS:S:C6 PS:S:S7 PS:S:Q8 }
                set slot1List { PS-SR-CORR- }
                set slot2List { PS-SR-CORR- }
                set slot3List { PS-SR-SEXT- }
                set slot4List { PS-SR-QUAD- }
                set slot5List { PS-SR-CORR- }
                set slot6List { PS-SR-CORR- }
                set slot7List { PS-SR-SEXT- }
                set slot8List { PS-SR-QUAD- }
            }
        }
        set selectedList ""
        set slotsInUseList ""
        set ETSNumberList ""
    }
}

##### Specific procedures for system 'sr' #####

proc APSSRDCPSSelectionFrame {widget args} {
    set parent ""
    set label "Power Supply Selections by Sector"
    set variablePrefix S
    APSStrictParseArguments {parent label}

    APSFrameGrid $widget -parent $parent -yList {top bot} 

    set w1 $parent$widget.bot

    set parent $parent$widget.top
    set widget .wsf
    set w $parent$widget.frame

    set widgetList ""
    set labelList ""
    for {set sector 1} {$sector<41} {incr sector} {
        lappend widgetList $w.s$sector
        lappend labelList [format %2s $sector]
    }

    APSWidgetSwapFrame $widget -parent $parent -label $label \
      -contextHelp "Provides selection of DC power supplies for SR" \
      -labelList $labelList -widgetList $widgetList \
      -height 280 -width 700 -buttonSize small -buttonLimitPerRow 20  -packOption "-fill x"
    pack propagate $parent$widget.frame 0
    pack $parent$widget -side top

    set HCList {AH1 AH2 AH3 AH4 BH4 BH3 BH2 BH1}
    set VCList {AV1 AV2 AV3 AV4 BV4 BV3 BV2 BV1}
    set QList  {AQ1 AQ2 AQ3 AQ4 AQ5 BQ5 BQ4 BQ3 BQ2 BQ1}
    set SList  {AS1 AS2 AS3 AS4 BS3 BS2 BS1}
    set QS4List {A} 
    set CList {BM H1 V1} 
    set MTList {AMT BMT}
    set AQSSectors {3 7 11 15 19 23 27 31 35 39} 
    set BQSSectors {1 5  9 13 17 21 25 29 33 37}
    set CSectors {12 13 16 21 23 24 34}
    set MTSectors {36 37 38 39 40}
    global allSelectionVars HCSelectionVars VCSelectionVars QSelectionVars SSelectionVars QSSelectionVars QS4SelectionVars CSelectionVars MTSelectionVars
    set allSelectionVars ""
    set HCSelectionVars ""
    set VCSelectionVars ""
    set QSelectionVars ""
    set SSelectionVars ""
    set QSSelectionVars ""
    set QS4SelectionVars ""
    set CSelectionVars ""
    set MTSelectionVars ""
    for {set sector 1} {$sector<41} {incr sector} {
        APSFrame .s$sector -parent $w -noPack 1 -label "Sector $sector" 
        set allSectorVars ""
        foreach type {HC VC Q S QS4 C MT} {
            if {($type == "C") && ([lsearch -exact $CSectors $sector]==-1)} {
                continue
            }
            if {($type == "MT") && ([lsearch -exact $MTSectors $sector]==-1)} {
                continue
            }
            set varList ""
            foreach item [subst \$${type}List] {
                global $variablePrefix$sector$item
                lappend varList $variablePrefix$sector$item
                lappend allSectorVars $variablePrefix$sector$item
                lappend allSelectionVars $variablePrefix$sector$item
                lappend ${type}SelectionVars $variablePrefix$sector$item
                set $variablePrefix$sector$item 0
            }
            APSCheckButtonFrame .t$type -parent $w.s$sector.frame -label [format %3s: $type] \
              -variableList $varList \
              -buttonList [subst \$${type}List] -orientation horizontal \
              -allNone 1 \
              -contextHelp "Select which converters you want to calibrate from this sector."
        }
        set skewType ""
        if [lsearch -exact $AQSSectors $sector]!=-1 {
            set skewType A
        } elseif [lsearch -exact $BQSSectors $sector]!=-1 {
            set skewType B
        }
        if [string length $skewType] {
            global $variablePrefix$sector${skewType}QS 
            lappend allSectorVars $variablePrefix$sector${skewType}QS 
            lappend allSelectionVars $variablePrefix$sector${skewType}QS
            lappend QSSelectionVars $variablePrefix$sector${skewType}QS
            APSCheckButtonFrame .tskew -parent $w.s$sector.frame -label " QS:" \
              -variableList $variablePrefix$sector${skewType}QS \
              -buttonList ${skewType} -allNone 0 -orientation horizontal \
              -contextHelp "Select which converters you want to calibrate from this sector."
        }
        if {$sector == 1} {
            global BMSelected
            lappend allSectorVars ${variablePrefix}BM
            APSCheckButtonFrame .tbm -parent $w.s$sector.frame -label "    " \
              -variableList SBM \
              -buttonList {"Main Dipole"} -allNone 0 -orientation horizontal \
              -contextHelp "Select this if you want to calibrate the Main Dipole"
        }

        APSButton .all -parent $w.s$sector.frame -text "All in sector" -command \
          "APSSetVariableListToValue 1 $allSectorVars" \
          -contextHelp "Selects all converters from this sector for calibration."
        APSButton .none -parent $w.s$sector.frame -text "None in sector" -command \
          "APSSetVariableListToValue 0 $allSectorVars" \
          -contextHelp "Selects all converters from this sector for calibration."
        global filterFiles
        set filterFiles No
        APSRadioButtonFrame .filterFiles -parent $w.s$sector.frame -label "Filter Files"\
          -buttonList {"Yes" "No"} \
          -valueList "Yes No" -variable filterFiles -orientation horizontal \
          -contextHelp "Allows selection of whether or not to filter the lists of calibration files shown by the Process/display and Display chosen details buttons according to the supplies selected.  If Yes, then you are assured that the files you are allowed to select from do in fact contain the supplies you've selected."
    }
    APSSwapInWidget $widget -parent $parent -swapIn $w.s1

    foreach item {Q S HC VC QS QS4 C MT} {
        APSButton .all$item -parent $w1 -text "Select all $item" \
          -command "APSSetVariableListToValue 1 [subst \$${item}SelectionVars]" \
          -contextHelp "Select all $item power supplies in all sectors."
    }

    return $allSelectionVars
}

proc APSSRReturnSelectedDCPS {} {
    set HCList {AH1 AH2 AH3 AH4 BH4 BH3 BH2 BH1}
    set VCList {AV1 AV2 AV3 AV4 BV4 BV3 BV2 BV1}
    set QList  {AQ1 AQ2 AQ3 AQ4 AQ5 BQ5 BQ4 BQ3 BQ2 BQ1}
    set SList  {AS1 AS2 AS3 AS4 BS3 BS2 BS1}
    set QS4List  {A}
    set CList {BM H1 V1} 
    set MTList {AMT BMT} 
    set HCListC {A:H1 A:H2 A:H3 A:H4 B:H4 B:H3 B:H2 B:H1}
    set VCListC {A:V1 A:V2 A:V3 A:V4 B:V4 B:V3 B:V2 B:V1}
    set QListC  {A:Q1 A:Q2 A:Q3 A:Q4 A:Q5 B:Q5 B:Q4 B:Q3 B:Q2 B:Q1}
    set SListC  {A:S1 A:S2 A:S3 A:S4 B:S3 B:S2 B:S1}
    set QS4ListC  {A:QS4}
    set CListC {C:BM C:H1 C:V1} 
    set MTListC {A:MT B:MT} 
    set AQSSectors {3 7 11 15 19 23 27 31 35 39} 
    set BQSSectors {1 5  9 13 17 21 25 29 33 37}
    set CSectors {12 13 16 21 23 24 34}
    set MTSectors {36 37 38 39 40}
    set selectedList ""
    global SBM
    if $SBM {
        lappend selectedList S:BM
    }
    for {set sector 1} {$sector<41} {incr sector} {
        foreach type {HC VC Q S QS4 C MT} {
            if {($type == "C") && ([lsearch -exact $CSectors $sector]==-1)} {
                continue
            }
            if {($type == "MT") && ([lsearch -exact $MTSectors $sector]==-1)} {
                continue
            }
            set index 0
            foreach item [subst \$${type}List] {
                global S$sector$item
                if [subst \$S$sector$item] {
                    lappend selectedList S$sector[lindex [subst \$${type}ListC] $index]
                }
                incr index
            }
        }
        set skewType ""
        if [lsearch -exact $AQSSectors $sector]!=-1 {
            set skewType A
        } elseif [lsearch -exact $BQSSectors $sector]!=-1 {
            set skewType B
        } 
        if [string length $skewType] {
            global S$sector${skewType}QS
            if [subst \$S$sector${skewType}QS] {
                lappend selectedList S$sector$skewType:QS
            }
        }
    }
    return $selectedList
}

proc SRInstallNewCalibration {args} {
    global status ETSNumber asloValue aoffValue magnetList

    APSStrictParseArguments {offsetLimit slopeDeviationLimit magnetList}

    switch [llength $magnetList] {

        0 {
            set status "must select magnet where calibration is to be updated"
            return
        }
        1 {
            # do nothing - this is the correct number
        }
        default {
            set status "select only one magnet"
            return
        }
    }

    set ETSNumber ""
    set asloValue ""
    set aoffValue ""
    global InstallWndo
    set InstallWndo [APSUniqueName .]
    APSWindow $InstallWndo -name "Install Converter Calibration"
    label ${InstallWndo}.label -text "New calibration for $magnetList"
    pack ${InstallWndo}.label -side top -in ${InstallWndo}.userFrame
    APSLabeledEntry .etsNum -parent ${InstallWndo}.userFrame -label "Converter ETS Number:" \
      -textVariable ETSNumber -width 15
    APSLabeledEntry .asloVal -parent ${InstallWndo}.userFrame -label "Converter ASLO value:" \
      -textVariable asloValue -width 15
    APSLabeledEntry .aoffVal -parent ${InstallWndo}.userFrame -label "Converter AOFF value:" \
      -textVariable aoffValue -width 15
    APSButton .install -parent ${InstallWndo}.buttonRow -text "Install" -highlightColor red -highlight 1 \
      -command { SRInstallNewConverterCalibration -ETSNumber $ETSNumber -magnet $magnetList \
                   -asloValue $asloValue -aoffValue $aoffValue -offsetLimit $offsetLimit \
                   -slopeDeviationLimit $slopeDeviationLimit } \
      -packOption "-side right" \
      -contextHelp "Install converter calibration in selected magnet location."
}

proc SRInstallNewConverterCalibration {args} {
    global status apsDCPSCalMainDir testStandDir 
    global asloValue aoffValue

    APSStrictParseArguments {ETSNumber magnet aoffValue asloValue offsetLimit slopeDeviationLimit}

    if {$ETSNumber != ""} {
        if {![APSValidatePSConverterETSNumber -ETSNumber $ETSNumber]} {
            set status "error: invalid ETS number entered"
            return
        }
        # have to find converter in psts database
        set files [lsort -decreasing [glob \
                                        -nocomplain ${apsDCPSCalMainDir}/${testStandDir}/CalData-????-???-????-??????.proc]]
        set mostRecentCalFile ""
        foreach file $files {
            set fd [sdds open $file r]
            if [catch {sdds getColumn $fd "PSName"} cols ] {
                set status "error reading file [lindex [split $file "/"] [expr [llength [split $file "/"]] - 1]]"
            } else {
                set ETSIndex [lsearch [sdds getColumn $fd "PSName"] $ETSNumber]
                if {$ETSIndex != -1} {
                    set mostRecentCalFile "${testStandDir}/[lindex [split $file "/"] [expr [llength [split $file "/"]] - 1]]"
                    set status "found calibration record ($mostRecentCalFile)"
                    set asloValue [format %-12.8f [lindex [sdds getColumn $fd "ASLO"] $ETSIndex]]
                    set aoffValue [format %-12.8f [lindex [sdds getColumn $fd "AOFF"] $ETSIndex]]
                    sdds close $fd
                    break
                }
            }
            sdds close $fd
        }
        if {$mostRecentCalFile == ""} {
            set status "No calibration record for $ETSNumber (enter ASLO and AOFF instead)"
            return
        }

    } else {

        if {([string length $asloValue] == 0) || ([string length $aoffValue] == 0)} {
            set status "Error: must enter either converter ETS number or both ASLO & AOFF values"
            return
        }
        # check that valid numbers have been entered
        if {![APSCheckIsNumber -value $asloValue]} {
            set status "Error: $asloValue is not a valid number"
            return
        }
        if {![APSCheckIsNumber -value $aoffValue]} {
            set status "Error: $aoffValue is not a valid number"
            return
        }
        # check range of entered numbers
        if {[expr abs($aoffValue)] > $offsetLimit} {
            set status "Error: specified AOFF value exceeds limit of $offsetLimit"
            return
        }
        if {[expr abs(1 - $asloValue)] > $slopeDeviationLimit} {
            set status "Error: specified ASLO value exceeds limit of $slopeDeviationLimit"
            return
        }
    }

    # now install values
    set oldvalues [exec cavget -pend=15 -float=%10.8f "-delim= " -list=${magnet}:CurrentAI -list=.ASLO,.AOFF]

    set status "Ready to install new calibration for ${magnet}"; update
    set message  "Warning: Calibration of the current readback for ${magnet} is about to be changed. Presently installed values are ASLO=[lindex $oldvalues 0]  and AOFF=[lindex $oldvalues 1]"
    if [APSQueryToProceed -message $message] {
        if [catch {exec cavput -pend=15 -list=${magnet}:CurrentAI.ASLO=${asloValue},${magnet}:CurrentAI.AOFF=${aoffValue}} result] {
            set status "$result" ; update
        } else {
            set status "New calibration installed" ; update
        }
    } else {
        set status "Aborted"
    }
    global InstallWndo
    destroy $InstallWndo
}

##### Specific procedures for system 'psts' #####

proc APSPSTestStandReturnSelectedDCPS {} {
    global pvRootList selectedList slotsInUseList ETSNumberList
    set selectedList ""
    set slotsInUseList ""
    set ETSNumberList ""

    for {set i 0} {$i < 8} {incr i} {
        set slot [expr $i + 1]
        global [subst slot${slot}Selection] [subst slot${slot}ETSNumber]
        if {[subst \$slot${slot}ETSNumber] != ""} {
            lappend selectedList [lindex $pvRootList $i]
            lappend slotsInUseList $slot
            lappend ETSNumberList [subst \$slot${slot}ETSNumber]
        }
    }

    if {[APSDCPSValidateETSNumbers] != 1} {
        set selectedList ""
        set slotsInUseList ""
        set ETSNumberList ""
    }
    return $selectedList

}

proc APSPutETSNumbersInDataFiles {args} {
    global selectedList ETSNumberList status calDescription
    APSStrictParseArguments {filename}

    set parStr ""
    if {[llength $ETSNumberList] == [llength $selectedList]} {
        for {set i 0} {$i < [llength $selectedList]} {incr i} {
            lappend parStr "-editnames=column,[lindex $selectedList $i]*,%/[lindex $selectedList $i]/[lindex $ETSNumberList $i]/"
        }
        catch [eval exec "sddsconvert $filename -nowarnings [join $parStr]"] status
        catch [exec /bin/rm ${filename}~] status
    }

    set parStr ""
    if {[llength $ETSNumberList] == [llength $selectedList]} {
        for {set i 0} {$i < [llength $selectedList]} {incr i} {
            lappend parStr "-reedit=column,PSName,%/[lindex $selectedList $i]/[lindex $ETSNumberList $i]/"
        }
        lappend parStr "-print=par,CalibrationDescription,$calDescription"
        if [catch {eval exec "sddsprocess ${filename}.calRec -nowarnings [join $parStr]"} result ] {
            set status "$result"; update
        }
        if [catch {exec /bin/rm ${filename}.calRec~} result ] {
            set status "$result"; update
        }
    }

}

proc DisplayPSTSCalibrationHistory {args} {
    global status apsDCPSCalMainDir system
    global ETSNumberList

    APSStrictParseArguments {unique}

    APSReturnSelectedDCPS -system $system

    set match ""
    foreach choice $ETSNumberList {
        lappend match "PSName=$choice"
    }

    switch [llength $match] {

        0 {
            set printouts {0 1}
            set matches ""
        }
        1 {
            set printouts {1}
            set matches "-match=col,$match"
        }
        2 {
            set printouts {1}
            set matches "-match=col,[join $match ,],|"
        }
        default {
            set status "cannot match more than two fields"
            return
        }
    }
    if {$unique} {
        set unique "-unique"
        set printouts {1}
    } else {
        set unique ""
    }

    set status "Compiling history of calibration records..."; update
    set files [lsort -decreasing [glob \
                                    -nocomplain ${apsDCPSCalMainDir}/${system}/CalData-????-???-????-??????.proc]]

    set tmpFile [APSTmpDir]/[APSUniqueName printout]
    if [catch {eval exec sddscombine [join $files " "] -pipe=out \
                 | sddsprocess -pipe $matches \
                 "-print=col,CalFile,%s,Filename" \
                 "-print=col,CalibrationDate,%s,TimeStamp" -nowarnings \
                 | sddscombine -pipe -merge \
                 | sddsprocess -pipe=in $tmpFile "-reedit=col,CalFile,10Z/%/.proc//"} result] {
        APSSetVarAndUpdate status "$result"
        return
    }

    foreach index $printouts {
        set sortFirst [lindex {"PSName,incr" "CalFile,dec"} $index]
        set sortLast [lindex {"CalFile,dec" "PSName,incr"} $index]
        if [catch {eval exec sddssort $tmpFile -pipe=out "-col=$sortFirst" \
                     | sddssort -pipe -col=$sortLast $unique \
                     | sddsprintout -pipe=in ${tmpFile}.$index \
                     \"-title=History of calibrations for converter test stand\" \
                     \"-column=PSName,label=ETS Number,format=%16s\" \
                     \"-column=CalibrationDate,label=Calibration Date,format=%26s\" \
                     -column=ASLO,format=%10.6f -column=AOFF,format=%10.6f \
                     \"-column=CalFile,label=Calibration File,format=%30s\" } result] {
            APSSetVarAndUpdate status "$result"
            return
        }
        APSFileDisplayWindow [APSUniqueName .] -fileName ${tmpFile}.$index -deleteOnClose 1 -width 110 \
          -height 20 -printCommand "enscript -r"
    }
    set status "Done"
}

proc APSPSTestStandSelectionFrame {widget args} {
    set parent ""
    set label "Converter Selection by Test Stand Slot"
    set variablePrefix S
    APSStrictParseArguments {parent label}

    APSFrame .title -parent $parent -label $label -height 1
    APSFrameGrid $widget -parent $parent -xList {left right} -yList {1 2 3 4} \
      -relief raised -bd 3 -packOption "-side top -anchor center" \
      -contextHelp "Enter ETS numbers of converters to be calibrated in the PS test stand. \
                     Each entry corresponds to one slot of the test stand."
    set w $parent$widget

    for {set slot 1} {$slot < 5} {incr slot} {
        global [subst slot${slot}List] [subst slot${slot}ETSNumber]
        APSLabeledEntry ${slot}ETSNumber -parent $w.left.${slot}.frame -textVariable [subst slot${slot}ETSNumber] \
          -label "[string range [subst \$slot${slot}List] 7 10] ETS#" -width 15 -packOption "-side right"
    }
    for {set slot 5} {$slot < 9} {incr slot} {
        set loc [expr $slot - 4]
        global [subst slot${slot}List] [subst slot${slot}ETSNumber]
        APSLabeledEntry ${slot}ETSNumber -parent $w.right.${loc}.frame -textVariable [subst slot${slot}ETSNumber] \
          -label "[string range [subst \$slot${slot}List] 7 10] ETS#" -width 15 -packOption "-side right"
    }

    APSLabeledEntry .description -parent $parent -width 52 -packOption "-side top -anchor w" \
      -label " Description:" -textVariable calDescription \
      -contextHelp "Enter a description of the calibration to be performed - eg 'Routine calibration', \
           'changed DAC board', etc"
    global filterFiles
    set filterFiles No
    APSRadioButtonFrame .filterFiles -parent $parent -label " Filter Files"\
      -buttonList {"Yes" "No"} \
      -valueList "Yes No" -variable filterFiles -orientation horizontal


}

proc APSDCPSValidateETSNumbers {args} {
    global status selectedList slotsInUseList ETSNumberList

    if {[llength $slotsInUseList] == 0} {
        set status "no converters selected"
        return 0
    }

    set go 1
    for {set i 0} {$i < [llength $slotsInUseList]} {incr i} {
        set ETSNumber [lindex $ETSNumberList $i]
        set slot [lindex $slotsInUseList $i]
        global [subst slot${slot}List]

        if {![APSValidatePSConverterETSNumber -ETSNumber $ETSNumber]} {
            set status "error: invalid ETS Number entered for slot $slot"
            set go 0
        } else {
            if {[lsearch -glob [subst \$slot${slot}List] [string range $ETSNumber 0 10]] == -1} {
                set status "error: slot $slot does not support converter type [string range $ETSNumber 6 9]"
                set go 0
            }
        }
    }
    return $go
}

proc APSValidatePSConverterETSNumber {args} {
    global ETSRootList
    APSStrictParseArguments {ETSNumber}

    if {([string length $ETSNumber] != 15) || \
          ([lsearch -glob  $ETSRootList [string range $ETSNumber 0 10]] == -1) || \
          ([string match {0[0-9][0-9][0-9]} [string range $ETSNumber 11 15]] != 1) } {
        return 0
    } else {
        return 1
    }
}

proc APSWritePSTestStandPVDefaults {} {
    global status DRVHList DRVLList ETSRootList ETSNumberList selectedList

    set putList ""
    for {set i 0} {$i < [llength $ETSNumberList]} {incr i} {
        set ETSNumber [lindex $ETSNumberList $i]
        set typeIndex [lsearch -glob  $ETSRootList [string range $ETSNumber 0 10]]
        lappend putList "[lindex $selectedList $i]:CurrentAO.DRVH=[lindex $DRVHList $typeIndex]"
        lappend putList "[lindex $selectedList $i]:CurrentAO.DRVL=[lindex $DRVLList $typeIndex]"
        lappend putList "[lindex $selectedList $i]:CurrentAO.ASLO=1.00"
        lappend putList "[lindex $selectedList $i]:CurrentAO.AOFF=0.00"
    }
    eval exec "cavput -pend=15 -list=[join $putList ,]"
}

##### System-selective procedures #####

proc APSReturnSelectedDCPS {args} {
    set system sr
    APSStrictParseArguments {system}
    switch $system {
        sr {
            return [APSSRReturnSelectedDCPS]
        }
        psts {
            return [APSPSTestStandReturnSelectedDCPS]
        }
    }

}

proc MakeCalibrationRun {} {
    global status system dataCollectionSetpoints percentOfRange calDescription
    global percentOfMaxStart
    global outputFile

    switch $system {

        sr {
            RunCalibration -system $system -PSList [APSReturnSelectedDCPS -system $system] \
              -dataPoints $dataCollectionSetpoints -percentOfRange $percentOfRange \
              -percentOfMaxStart $percentOfMaxStart
        }

        psts {
            if {[APSQueryToProceed -message "Warning: calibrations can only be performed from the EAA PS Test Cage"] != 1} {
                set status "nothing done"
                return
            }
            set PSList [APSReturnSelectedDCPS -system $system]

            if {[llength $PSList] != 0} {
                APSWritePSTestStandPVDefaults
            }

            if {$calDescription == ""} {
                set status "Must enter a description for this calibration run"
                return
            }

            RunCalibration -system $system -PSList $PSList \
              -dataPoints $dataCollectionSetpoints -percentOfRange $percentOfRange \
              -percentOfMaxStart $percentOfMaxStart

            if {[info exists outputFile] && [file exists $outputFile]} {
                APSPutETSNumbersInDataFiles -filename $outputFile
            }
        }

    }
}



proc DisplayChosenDetails {args} {
    set showWorst 0
    set number 10
    APSStrictParseArguments {showWorst number}
    global ETSNumberList system showRaw

    switch $system {
        sr {
            if !$showWorst {
                DisplayCalibrationDetails -system $system \
                  -PSList [APSReturnSelectedDCPS -system $system] \
                  -showRaw $showRaw 
            } else {
                set worstList [APSReturnWorstDCPS -system $system -number $number]
                if [llength $worstList]<2 {
                    APSSetVarAndUpdate status "Problem finding worst supplies."
                    return
                }
                set runName [lindex $worstList 0]
                set supplies [lrange $worstList 1 end]
                DisplayCalibrationDetails -system $system \
                  -PSList $worstList -showRaw $showRaw \
                  -runName $runName
            }
        }
        psts {
            APSReturnSelectedDCPS -system $system
            DisplayCalibrationDetails -system $system -PSList $ETSNumberList -showRaw $showRaw
        }
    }
}

proc APSReturnWorstDCPS {args} {
    global apsDCPSCalMainDir
    set system ""
    set number ""
    APSStrictParseArguments {system number}
    set choice [ChooseCalibrationFile -system $system]
    if ![string length $choice] return
    set filename $apsDCPSCalMainDir/$system/CalData-$choice.proc
    if ![file exists $filename] {
        APSSetVarAndUpdate status "Processed data not found.  Process the data first."
        return ""
    }
    if [catch {exec sddssort $filename -pipe=out -column=LFSD,decreasing \
                 | sddsprocess -pipe -filter=column,LFSD,0,1e300 \
                 -clip=$number,0,invert \
                 | sdds2stream -pipe -column=PSName} PSList] {
        APSSetVarAndUpdate status "APSReturnWorstDCPS: $PSList"
        return ""
    }
    return [concat $choice $PSList]
}

#### Common procedures #####

proc APSCheckIsNumber {args} {
    APSStrictParseArguments {value}

    if {![regexp {^[0-9.]+$} $value]} {
        return 0
    } else {
        return 1
    }
}

proc APSSetVariableListToValue {value args} {
    foreach item $args {
        global $item
        set ${item} $value
    }
}

proc APSGetDCPSCalibration {args} {
    set output ""
    set PSList ""
    set quantity ""
    set fieldList ""
    APSStrictParseArguments {output PSList quantity fieldList}

    set tmpRoot [APSTmpDir]/[APSTmpString]
    set tmpOut $tmpRoot.out
    set oldDir [pwd]
    cd /tmp
    APSAddToTempFileList $tmpRoot.1 $tmpOut
    if [catch {exec cavget -pend=120 -list=[join $PSList ,] \
                 -list=:$quantity. -list=[join $fieldList ,] -label \
                 |  csv2sdds -pipe "-separator= " -maxRows=[expr [llength $PSList]*[llength $fieldList]] \
                 -columnData=name=ControlName,type=string \
                 -columnData=name=Value,type=double \
                 | sddsprocess -pipe=in $tmpRoot.1 \
                 -edit=column,PSName,ControlName,S/:Current/20D} result] {
        APSSetVarAndUpdate status "$result"
        cd $oldDir
        return -code error "$result"
    }
    set first 1
    foreach field $fieldList {
        APSAddToTempFileList $tmpRoot.$field
        if [catch {exec sddsprocess $tmpRoot.1 -pipe=out \
                     -match=column,ControlName=*$field \
                     | sddsconvert -pipe=in $tmpRoot.$field \
                     -dele=column,ControlName -rename=column,Value=$field } result] {
            APSSetVarAndUpdate status "$result"
            cd $oldDir
            return -code error "$result"
        }
        if $first {
            set first 0
            if [catch {exec mv $tmpRoot.$field $tmpOut} result] {
                APSSetVarAndUpdate status "$result"
                cd $oldDir
                return -code error "$result"
            }
        } else {
            if [catch {exec sddsxref -nowarning $tmpOut $tmpRoot.$field -take=$field} \
                  result] {
                APSSetVarAndUpdate status "$result"
                cd $oldDir
                return -code error "$result"
            }
        }
        APSSetVarAndUpdate status "Done with $field"
    }
    cd $oldDir
    if [catch {exec cp $tmpOut $output} result] {
        APSSetVarAndUpdate status "$result"
        return -code error "$result"
    }
}

proc DisplayCalibrationDetails {args} {
    set system sr
    set PSList ""
    set showRaw 0
    set runName ""
    APSStrictParseArguments {system PSList showRaw runName}
    global apsDCPSCalMainDir status

    if [llength $PSList]==0 {
        APSSetVarAndUpdate status "Nothing chosen for display."
        return
    }
    if ![string length $runName] {
        set choice [ChooseCalibrationFile -system $system -PSList $PSList]
    } else {
        set choice $runName
    }
    if [catch {exec sdds2stream -rows $apsDCPSCalMainDir/$system/CalData-$choice} rows] {
        APSSetVarAndUpdate status "Problem with file: $rows"
        return
    }
    set rows [lindex [split $rows] 0]
    if $rows<2 {
        APSSetVarAndUpdate status "Insufficient data in file to do fits."
        return
    }

    if {$showRaw} {
        set fitList {CurrentAI.RVAL}
    } else {
        set fitList {CurrentAI}
    }
    set tmpRoot [APSTmpDir]/[APSTmpString]
    set tmpFileList ""
    set plotOptions ""
    # check to see that the requested item exists in the chosen file
    set fd [sdds open $apsDCPSCalMainDir/$system/CalData-$choice r]
    set cols [sdds getNames $fd column]
    sdds close $fd

    set PSList1 ""
    foreach item $PSList {
        if {[lsearch $cols ${item}:CurrentAO] == -1} {
            APSSetVarAndUpdate status "$item is not in chosen file"
        } else {
            lappend PSList1 $item
        }
    }
    set PSList $PSList1
    if [expr [llength $PSList]>20] {
        if [APSMultipleChoice [APSUniqueName .] \
              -question "This make take a long time.  It is suggested that you pick out only the supplies that are interesting based on the sorted lists from processing.  Use the power supply selections to limit the number that will be displayed." \
              -labelList {"Do it anyways" "Cancel"} \
              -returnList {0 1}] {
            APSSetVarAndUpdate status "Display cancelled."
            return
        }
    }

    foreach item $PSList {
        APSSetVarAndUpdate status "Working on $item"
        foreach fitType $fitList {
            set tmpFile $tmpRoot.$item.$fitType
            if [catch {exec sddspfit $apsDCPSCalMainDir/$system/CalData-$choice -pipe=out \
                         -columns=$item:CurrentAO,$item:$fitType -terms=2 \
                         | sddsprocess -pipe=in $tmpFile \
                         "-print=parameter,Label1,RmsResidual: %.4f,RmsResidual"} result] {
                APSSetVarAndUpdate status "Problem fitting for $item: $result"
                continue
            } else {
                lappend plotOptions -column=$item:CurrentAO,$item:${fitType}Fit -graph=line \
                  $tmpFile \
                  -column=$item:CurrentAO,$item:${fitType} -graph=symbol,scale=1.5 \
                  $tmpFile -end
                lappend plotOptions -column=$item:CurrentAO,$item:${fitType}Residual \
                  $tmpFile -end
            }
            lappend tmpFileList $tmpFile
        }
    }
    if [llength $plotOptions] {
        set status "Plotting..."
        eval exec sddsplot -graph=symbol,scale=1.5,type=5,subtype=4,connect=0 \
          -topline=@sddspfitLabel -title=@Label1 -toptitle $plotOptions &
        after 30000 "eval file delete $tmpFileList"
        set status "Done"
    } else {
        set status "Nothing to plot"
    }
}

proc RunCalibration {args} {
    global systemDescription
    set system sr
    set PSList ""
    set dataPoints 20
    set percentOfRange 90
    set percentOfMaxStart 10
    APSStrictParseArguments {system PSList dataPoints percentOfRange percentOfMaxStart}

    if [llength $PSList]==0 {
        APSSetVarAndUpdate status "Nothing chosen for calibration."
        return
    }
    if [string match $system sr] {
        if {[catch {exec cavget -list=S-DCCT:CurrentM} beamCurrent] || [expr abs($beamCurrent)>0.1]} {
            APSSetVarAndUpdate status "Can't calibrate---appears that there is stored beam."
            return
        }
    }

    if [catch {exec cavget -pend=120 -list=[join $PSList ,] -list=:StatusCALC -label} statusList] {
        APSSetVarAndUpdate status "Can't get PS status: $statusList"
        return
    }
    set origPSList $PSList
    set PSNotOff0 [APSSearchAssociateListForValue -list $statusList -value 0]
    set PSNotOff1 [APSSearchAssociateListForValue -list $statusList -value 1]
    set PSNotOff [concat $PSNotOff0 $PSNotOff1]
    regsub -all :StatusCALC $PSNotOff "" PSList
    APSSetVarAndUpdate status "Calibrating [llength $PSList] power supplies for $systemDescription."
    if [llength $PSList]!=[llength $origPSList] {
        APSSetVarAndUpdate status "[llength $origPSList] requested but some are off."
    }

    set tmpRoot [APSTmpDir]/[APSTmpString]
    global apsDCPSCalMainDir outputFile
    set outputFile $apsDCPSCalMainDir/$system/[exec date +CalData-%Y-%j-%m%d-%H%M%S]
    if [file exists $outputFile] {
        APSSetVarAndUpdate status "Automatically-generated output file $outputFile already exists!"
        return
    }

    APSSetVarAndUpdate status "Getting current calibration constants"
    if [catch {APSGetDCPSCalibration -output $outputFile.calRec \
                 -PSList $PSList -quantity CurrentAI \
                 -fieldList {ESLO EGUL EGUF ASLO AOFF ROFF} } result] {
        APSSetVarAndUpdate status "$result"
        return
    }

    APSSetVarAndUpdate status "Setting up sddsexperiment input file."
    if {[catch {exec cavget -pend=120 -list=[join $PSList ,] -list=:CurrentAO.DRVH} DRVHList]} { 
        APSSetVarAndUpdate status "Problem reading high drive limits: $DRVHList"
        return
    } 
    APSSetVarAndUpdate status "Got DRVH values"
    if {[catch {exec cavget -pend=120 -list=[join $PSList ,] -list=:CurrentAO.DRVL} DRVLList]} {
        APSSetVarAndUpdate status "Problem reading low drive limits: $DRVLList"
        return
    }
    APSSetVarAndUpdate status "Got DRVL values"

    if [catch {open $tmpRoot.input w} fd] {
        APSSetVarAndUpdate status "$fd"
        return
    }
    set index 0
    foreach PS $PSList {
        if {[string match S:BM $PS]} {
            set initialValue 50
        } else {
            set DRVL [lindex $DRVLList $index]
            set DRVH [lindex $DRVHList $index]
            if $DRVL==0 {
                set initialValue [expr $DRVH*$percentOfMaxStart/100.0]
            } else {
                set initialValue [expr $DRVL*$percentOfRange/100.0]
            }
        }
        set finalValue   [expr [lindex $DRVHList $index]*$percentOfRange/100.0]
        puts $fd "&variable control_name=$PS:CurrentAO initial_value=$initialValue final_value=$finalValue index_limit=$dataPoints index_number=0 units=A reset_to_original=1 &end"
        puts $fd "&measurement control_name=$PS:CurrentAI number_to_average=10 units=A &end"
        puts $fd "&measurement control_name=$PS:CurrentAI.RVAL number_to_average=10 units=? &end"
        if [string match $system sr] {
            if {[string match *:Q\[12345\]* $PS] || [string match *:S* $PS]} {
                puts $fd "&measurement control_name=$PS:OutVoltageAI number_to_average=10 units=V &end"
                puts $fd "&measurement control_name=$PS:MagTempAI number_to_average=10 units=C &end"
            } elseif {[string match *C:\[HV\]* $PS]} {
                puts $fd "&measurement control_name=$PS:OutVoltageAI number_to_average=10 units=V &end"
                puts $fd "&measurement control_name=$PS:MagTempAI number_to_average=10 units=C &end"
            } elseif {[string match *C:BM* $PS]} {
                puts $fd "&measurement control_name=$PS:OutVoltageAI number_to_average=10 units=V &end"
                puts $fd "&measurement control_name=$PS:MagTemp1AI number_to_average=10 units=C &end"
                puts $fd "&measurement control_name=$PS:MagTemp2AI number_to_average=10 units=C &end"
                puts $fd "&measurement control_name=$PS:MagTemp3AI number_to_average=10 units=C &end"
            } elseif {[string match *:\[HV\]* $PS]} {
                puts $fd "&measurement control_name=$PS:MagTempAI number_to_average=10 units=C &end"
            } elseif {[string match *:MT* $PS]} {
                puts $fd "&measurement control_name=$PS:MagTempAI number_to_average=10 units=C &end"
            }
        }
        incr index
    }
    if {[lsearch $PSList S:BM] != -1} {
        APSSetVarAndUpdate status "Warning: Calibration of the Main Dipole takes longer than other converters"
        puts $fd "&execute post_change_pause=30 intermeasurement_pause=1 rollover_pause=30 ramp_steps=20 ramp_pause=2 &end"
    } else {
        puts $fd "&execute post_change_pause=15 intermeasurement_pause=1 rollover_pause=30 &end"
    }
    close $fd

    APSSetVarAndUpdate status "Running calibration... [exec date +%H:%M:%S]"
    global DCPSCalibrationDone 
    set DCPSCalibrationDone 0
    APSExecLog [APSUniqueName .] -unixCommand \
      "sddsexperiment $tmpRoot.input $outputFile -verbose -summarize " \
      -width 100 -height 30 \
      -abortCallback {set DCPSCalibrationDone 2} \
      -cancelCallback {set DCPSCalibrationDone 2} \
      -callback {set DCPSCalibrationDone 1} 
    # don't use tkwait here because I want the display to stay live.
    while 1 {
        if $DCPSCalibrationDone break
        after 1000
        update
    }
    switch $DCPSCalibrationDone {
        1 {
            APSSetVarAndUpdate status "Calibration done."
        } 
        2 {
            APSSetVarAndUpdate status "Calibration aborted or cancelled."
        }
    }
}

proc ChooseCalibrationFile {args} {
    global systemDescription
    set system sr
    set PSList empty
    APSStrictParseArguments {system PSList}
    global apsDCPSCalMainDir
    if ![file isdirectory $apsDCPSCalMainDir/$system] {
        APSSetVarAndUpdate status "No directory found for $systemDescription"
        return
    }
    set calFiles0 [lsort -decreasing [glob -nocomplain $apsDCPSCalMainDir/$system/CalData-????-???-????-??????]]
    if ![llength $calFiles0] {
        APSSetVarAndUpdate status "No files found for $system"
        return
    }
    regsub -all $apsDCPSCalMainDir/$system/CalData- $calFiles0 {} calFiles
    global filterFiles
    if [string match $filterFiles Yes] {
        if ![string match $PSList empty] {
            set newFile 0
            set Files ""
            foreach cal $calFiles {
                append Files "$apsDCPSCalMainDir/$system/CalData-$cal.query "
                if {![file exists $apsDCPSCalMainDir/$system/CalData-$cal.query]} {
                    set newFile 1
                    APSSetVarAndUpdate status "Found new file"
                    APSSetVarAndUpdate status "$apsDCPSCalMainDir/$system/CalData-$cal.query"
                    APSSetVarAndUpdate status "Adding it to list"
                    if [catch {exec sddsquery $apsDCPSCalMainDir/$system/CalData-$cal -columnList\
                                 -sddsOutput=$apsDCPSCalMainDir/$system/CalData-$cal.query} results] {
                        APSSetVarAndUpdate status $results
                        return
                    }
                }
            }
            if {$newFile || ![file exists $apsDCPSCalMainDir/$system/CalDataDCPS.combined.$system]} {
                APSSetVarAndUpdate status "Combining $system files"
                if [catch {eval exec sddscombine $Files -pipe=out -retain=column,Name \
                             | sddsprocess -pipe -print=column,FileName,%s,Filename \
                             | sddscombine -pipe=in $apsDCPSCalMainDir/$system/CalDataDCPS.combined.$system -merge} results] {
                    APSSetVarAndUpdate status $results
                    return
                }
            }
            set passed "-match=column,Name=[lindex $PSList 0]*"
            foreach PS [lrange $PSList 1 end] {
                append passed ",Name=$PS*,|"
            }
            if [catch {eval exec sddsprocess $apsDCPSCalMainDir/$system/CalDataDCPS.combined.$system \
                         -pipe=output $passed\
                         | sddssort -pipe -column=FileName,decreasing -unique\
                         | sdds2stream -pipe -column=FileName} results] {
                APSSetVarAndUpdate status "No matches: $results" 
                return
            }
            regsub -all $apsDCPSCalMainDir/$system/CalData- $results {} calFiles2
            regsub -all .query $calFiles2 {} calFiles3
            regsub -all \n $calFiles3 { } calFiles
        }
    }
    set w .searchResult 
    if [winfo exists $w] {
        destroy $w
    }
    set returnList [APSListSelectDialog [APSUniqueName .] -name "FileChoiceList" \
                      -itemList $calFiles -contextHelp "List of available calibrations."]
    if {[llength $returnList] > 1} {
        APSSetVarAndUpdate status "Only one file can be selected at a time."
        return
    }
    return $returnList
}

proc ProcessCalibrationChoice {args} {
    set system sr
    set SDLimit 1
    set offsetLimit 1000
    set slopeDeviationLimit 0.1
    APSStrictParseArguments {system SDLimit offsetLimit slopeDeviationLimit PSList}
    global apsDCPSCalMainDir
    set choice [ChooseCalibrationFile -system $system -PSList $PSList]
    if ![string length $choice] {
        APSSetVarAndUpdate status "Calibration processing cancelled."
        return
    }
    set action Reprocess
    if [file exists $apsDCPSCalMainDir/$system/CalData-$choice.proc] {
        set action [APSMultipleChoice [APSUniqueName .] \
                      -question "$choice already processed. What do you want to do?" \
                      -labelList {Reprocess Display Cancel} \
                      -returnList {Reprocess Display Cancel}]
        if [string match $action Cancel] {
            APSSetVarAndUpdate status "Calibration processing cancelled."
            return
        }
    }
    if [string match $action Reprocess] {
        APSSetVarAndUpdate status "Processing $choice"
        if [catch {APSProcessDCPSCurrentCalibration -statusCallback "APSSetVarAndUpdate status" \
                     -dataFile $apsDCPSCalMainDir/$system/CalData-$choice -plot 1 \
                     -outputFile $apsDCPSCalMainDir/$system/CalData-$choice.proc} result] {
            APSSetVarAndUpdate status "$result"
            return
        }
        if [string match $system sr] {
            if [catch {APSProcessDCPSVoltageResponse -statusCallback "APSSetVarAndUpdate status" \
                         -dataFile $apsDCPSCalMainDir/$system/CalData-$choice -plot 1 \
                         -outputFile $apsDCPSCalMainDir/$system/CalData-$choice.Vproc} result] {
                APSSetVarAndUpdate status "$result"
                file delete $apsDCPSCalMainDir/$system/CalData-$choice.Vproc
            }
            if [catch {APSProcessDCPSMagTempResponse -statusCallback "APSSetVarAndUpdate status" \
                         -dataFile $apsDCPSCalMainDir/$system/CalData-$choice -plot 1 \
                         -outputFile $apsDCPSCalMainDir/$system/CalData-$choice.MTproc} result] {
                APSSetVarAndUpdate status "$result"
                file delete $apsDCPSCalMainDir/$system/CalData-$choice.MTproc
            }
        }
    }

    set tmpRoot [APSTmpDir]/[APSTmpString]
    set plotOptions ""
    APSAddToTempFileList $tmpRoot.histdata
    if [catch {exec sddsprocess $apsDCPSCalMainDir/$system/CalData-$choice.proc \
                 -pipe=out \
                 -nowarning \
                 -filter=column,LFSD,0,$SDLimit \
                 -filter=column,AOFF,[expr -$offsetLimit],$offsetLimit \
                 -filter=column,ASLO,[expr 1-abs($slopeDeviationLimit)],[expr 1+abs($slopeDeviationLimit)] \
                 | tee $tmpRoot.histdata \
                 | sdds2stream -rows -pipe} result] {
        return -code error "ProcessCalibrationChoice: $result"
    }
    if [string match $result "0 rows"] {
        APSSetVarAndUpdate status "Warning: No supplies pass limit criteria---no histograms created."
    } else {
        foreach column {ASLO AOFF LFSD} {
            set hasOld 0
            if [lsearch [list ASLO AOFF] $column]!=-1 {
                set hasOld 1
            }
            if [catch {exec sddsmultihist $tmpRoot.histdata -pipe=out -abscissa=$column \
                         -columns=$column,${column}Old -bins=50 \
                         | sddsprocess -pipe=in $tmpRoot.$column.hist \
                         -process=*Frequency,max,%sMax \
                         "-redefine=column,%sNormalized,%s %sMax /,select=*Frequency,units=" } result] {
                return -code error "ProcessCalibrationChoice: $result"

            }
            lappend plotOption -column=$column,${column}FrequencyNormalized \
              -xlabel=$column -ylabel=NormalizedFrequency \
              $tmpRoot.$column.hist 
            if $hasOld {
                lappend plotOption -legend=spec=New \
                  -column=$column,${column}OldFrequencyNormalized \
                  -legend=spec=Old \
                  $tmpRoot.$column.hist 
            }
            lappend plotOption -end
        }
        eval exec sddsplot {"-topline=Histograms for SD<$SDLimit A  |1-ASLO|<$slopeDeviationLimit  |AOFF|<$offsetLimit"} \
          -graph=line,vary $plotOption &
    }

    set tmpFile [APSTmpDir]/[APSTmpString]
    if [catch {exec sddsprintout $apsDCPSCalMainDir/$system/CalData-$choice.proc \
                 "-title=Results of calibration $choice of $system supplies" \
                 $tmpFile -column=PSName -column=AOFF -column=AOFFOld \
                 -column=ASLO -column=ASLOOld -column=LFSD} result] {
        APSSetVarAndUpdate status "$result"
        return
    }
    lappend displayFile $tmpFile
    lappend titleList "Calibration data"
    set sortName(AOFF) "linear fit Offset (AOFF)"
    set sortName(ASLO) "linear fit Slope (ASLO)"
    set sortName(LFSD) "Linear Fit Standard Deviation (LFSD)"
    foreach sort {AOFF ASLO LFSD} {
        if [catch {exec sddssort $apsDCPSCalMainDir/$system/CalData-$choice.proc \
                     -pipe=out -column=$sort,decreasing \
                     | sddsprocess -pipe -define=column,Rank,i_row,type=short \
                     | sddsprintout -pipe=in $tmpFile.$sort \
                     "-title=\n\nResults of calibration $choice of $system supplies sorted by $sortName($sort)\nWorst supplies are shown at the top." \
                     -column=PSName -column=AOFF -column=AOFFOld \
                     -column=ASLO -column=ASLOOld -column=LFSD -column=Rank} result] {
            APSSetVarAndUpdate status "$result"
            return
        }
        lappend titleList "Calibration data sorted by $sort"
        lappend displayFile $tmpFile.$sort
    }

    if [string match $system psts] {
        if [catch {eval exec cat $displayFile > $tmpFile.all} result] {
            return -code error "$result"
        }
        APSFileDisplayWindow [APSUniqueName .] -fileName $tmpFile.all \
          -deleteOnClose 1 -width 110 -height 40 -printCommand "enscript -r" \
          -sddsExportableFile $apsDCPSCalMainDir/$system/CalData-$choice.proc
    } else {
        foreach file $displayFile title $titleList {
            APSFileDisplayWindow [APSUniqueName .] -fileName $file \
              -comment $title \
              -deleteOnClose 1 -width 110 -height 40 -printCommand "enscript -r" \
              -sddsExportableFile $apsDCPSCalMainDir/$system/CalData-$choice.proc
        }
    }

    if [file exists $apsDCPSCalMainDir/$system/CalData-$choice.Vproc] {
        if [catch {exec sddssort $apsDCPSCalMainDir/$system/CalData-$choice.Vproc -pipe=out \
                     -column=LFSD,decreasing \
                     | sddsprintout -pipe=in $tmpFile.Vproc \
                     "-title=Fits of voltage response from calibration scan of $system supplies sorted by LFSD\nWorst supplies are shown at the top" \
                     -column=PSName -column=LFSD -column=Slope -column=Intercept} result] {
            APSSetVarAndUpdate status "$result"
            return
        }
        APSFileDisplayWindow [APSUniqueName .] -fileName $tmpFile.Vproc \
          -deleteOnClose 1 -width 132 -height 40 -printCommand "enscript -r"
    }
    if [file exists $apsDCPSCalMainDir/$system/CalData-$choice.MTproc] {
    }

}

proc InstallCalibrationChoice {args} {
    set system sr
    set SDLimit 1
    set offsetLimit 1000
    set slopeDeviationLimit 0.1
    APSStrictParseArguments {system SDLimit offsetLimit slopeDeviationLimit}
    global apsDCPSCalMainDir

    set choice [ChooseCalibrationFile -system $system]
    if ![string length $choice] {
        APSSetVarAndUpdate status "Calibration installation cancelled."
        return
    }

    if ![file exists $apsDCPSCalMainDir/$system/CalData-$choice.proc] {
        APSSetVarAndUpdate status "$choice has not been processed."
        return
    }
    set debug 0
    if !$debug {
        if {![APSMultipleChoice [APSUniqueName .] -question \
                "Confirm installation of new DC PS calibration values from $choice with fit SD limit of $SDLimit Amps, |1-ASLO|<$slopeDeviationLimit, and |AOFF|<$offsetLimit" \
                -labelList {Yes No} -returnList {1 0}]} {
            APSSetVarAndUpdate status "Installation cancelled."
            return
        }
    }
    if [catch {APSInstallDCPSCurrentCalibration -statusCallback \
                 "APSSetVarAndUpdate status" -SDLimit $SDLimit -debug $debug \
                 -slopeDeviationLimit $slopeDeviationLimit -offsetLimit $offsetLimit \
                 -dataFile $apsDCPSCalMainDir/$system/CalData-$choice.proc} result] {
        APSSetVarAndUpdate status "$result"
    }
}

proc APSInstallDCPSCurrentCalibration {args} {
    set statusCallback APSNoOp
    set dataFile ""
    set SDLimit 1
    set offsetLimit 1000
    set slopeDeviationLimit 0.1
    set debug 0
    APSStrictParseArguments {statusCallback dataFile SDLimit offsetLimit slopeDeviationLimit debug}

    if ![file exists $dataFile] {
        return -code error "APSInstallDCPSCurrentCalibration: Not found: $dataFile"
    }

    set tmpRoot [APSTmpDir]/[APSTmpString]
    set fileList ""
    foreach field {AOFF ASLO} {
        APSAddToTempFileList $tmpRoot.$field
        lappend fileList $tmpRoot.$field
        if [catch {exec sddsprocess $dataFile -pipe=out \
                     -filter=column,LFSD,0,$SDLimit \
                     -filter=column,AOFF,[expr -$offsetLimit],$offsetLimit \
                     -filter=column,ASLO,[expr 1-abs($slopeDeviationLimit)],[expr 1+abs($slopeDeviationLimit)] \
                     -print=parameter,SnapType,Absolute \
                     -print=column,ControlName,%s:CurrentAI.$field,PSName \
                     -print=column,ValueString,%21.15e,$field \
                     -print=column,ControlType,pv \
                     -print=column,Lineage,- \
                     -define=column,Count,1,type=long \
                     | sddsconvert -pipe=in $tmpRoot.$field \
                     -retain=col,ControlName,ValueString,ControlType,Lineage,Count } result] {
            return -code "APSInstallDCPSCurrentCalibration: $result"
        }
    }
    APSAddToTempFileList $tmpRoot
    if [catch {eval exec sddscombine $fileList $tmpRoot -merge} result] {
        return -code "APSInstallDCPSCurrentCalibration: $result"
    }
    if {[string length $statusCallback] && \
          ![string match $statusCallback APSNoOp]} {
        eval $statusCallback {"Sending [exec sdds2stream -rows $tmpRoot | token -n=1] values"}
    }
    if !$debug {
        if [catch {exec sddscasr -restore $tmpRoot} result] {
            return -code "APSInstallDCPSCurrentCalibration: $result"
        }
    } else {
        APSAddToTempFileList $tmpRoot.print
        if [catch {exec sddsprintout -column $tmpRoot $tmpRoot.print} result] {
            return -code "APSInstallDCPSCurrentCalibration: $result"
        }
        APSFileDisplayWindow [APSUniqueName .] -fileName $tmpRoot.print \
          -deleteOnClose 1 -height 40 -printCommand "enscript -r"
    }
}

proc APSProcessDCPSCurrentCalibration {args} {
    set statusCallback APSNoOp
    set dataFile ""
    set outputFile ""
    set plot 0
    APSStrictParseArguments {statusCallback dataFile outputFile plot}

    if ![file exists $dataFile] {
        return -code error "APSProcessDCPSCurrentCalibration: $dataFile"
    }

    if [catch {exec sddsquery $dataFile -sddsOutput -column \
                 | sddsprocess -pipe -match=column,Name=*CurrentAI.RVAL -nowarning \
                 | sdds2stream -pipe -column=Name} RVALList] {
        return -code error "APSProcessDCPSCurrentCalibration: $RVALList"
    }
    if [llength $RVALList]==0 {
        return -code error "APSProcessDCPSCurrentCalibration: No CurrentAI.RVAL data found!"
    }
    regsub -all CurrentAI.RVAL $RVALList CurrentAO AOList 
    eval $statusCallback {"[llength $RVALList] calibration measurements found."}
    set processOptions ""
    set index 0
    foreach item $AOList {
        lappend processOptions -process=$item,slope,${item}Slope,functionOf=[lindex $RVALList $index]
        lappend processOptions -process=$item,intercept,${item}Intercept,functionOf=[lindex $RVALList $index]
        lappend processOptions -process=$item,lfsd,${item}LFSD,functionOf=[lindex $RVALList $index]
        incr index
    }
    if [catch {eval exec sddsprocess -nowarning $dataFile -pipe=out $processOptions \
                 | sddscollapse -pipe \
                 | tee [APSTmpDir]/junk1 \
                 | sddscollect -pipe \
                 -collect=suffix=Slope -collect=suffix=Intercept -collect=suffix=LFSD \
                 | sddsprocess -pipe -nowarning \
                 -edit=column,PSName,Rootname,%/:CurrentAO// \
                 | tee [APSTmpDir]/junk2 \
                 | sddsxref -pipe $dataFile.calRec -match=PSName \
                 -take=* -nowarning \
                 | tee [APSTmpDir]/junk3 \
                 | sddsconvert -pipe -rename=col,AOFF=AOFFOld \
                 -rename=column,ASLO=ASLOOld \
                 | tee [APSTmpDir]/junk4 \
                 | sddsprocess -pipe=in $outputFile -nowarning \
                 {"-define=column,ASLO,Slope ESLO /,units=A"} \
                 {"-define=column,AOFF,Intercept EGUL - Slope ROFF * - ESLO /,units=A"} } result] {
        return -code error "APSProcessDCPSCurrentCalibration: $result"
    }
}

proc APSProcessDCPSVoltageResponse {args} {
    set statusCallback APSNoOp
    set dataFile ""
    set outputFile ""
    set plot 0
    APSStrictParseArguments {statusCallback dataFile outputFile plot}

    if ![file exists $dataFile] {
        return -code error "APSProcessDCPSVoltageResponse: $dataFile"
    }

    if [catch {exec sddsquery $dataFile -sddsOutput -column \
                 | sddsprocess -pipe -match=column,Name=*OutVoltageAI \
                 | sdds2stream -pipe -column=Name} VoltageList] {
        return -code error "APSProcessDCPSVoltageResponse: $VoltageList"
    }
    if [llength $VoltageList]==0 {
        eval $statusCallback {"No voltages measured---no voltage response processing done."}
        return
    }

    regsub -all OutVoltageAI $VoltageList CurrentAO AOList 
    eval $statusCallback {"[llength $VoltageList] calibration measurements found."}
    set processOptions ""
    set index 0
    foreach item $VoltageList {
        lappend processOptions -process=$item,slope,${item}Slope,functionOf=[lindex $AOList $index]
        lappend processOptions -process=$item,Intercept,${item}Intercept,functionOf=[lindex $AOList $index]
        lappend processOptions -process=$item,lfsd,${item}LFSD,functionOf=[lindex $AOList $index]
        incr index
    }
    if [catch {eval exec sddsprocess $dataFile -pipe=out \
                 $processOptions -nowarning \
                 | sddscollapse -pipe \
                 | sddscollect -pipe \
                 -collect=suffix=Slope -collect=suffix=Intercept \
                 -collect=suffix=LFSD \
                 | sddsprocess -pipe=in $outputFile \
                 -edit=column,PSName,Rootname,%/:OutVoltageAI//} result] {
        return -code error "APSProcessDCPSVoltageResponse: $result"
    }
}


proc APSProcessDCPSMagTempResponse {args} {
    set statusCallback APSNoOp
    set dataFile ""
    set outputFile ""
    set plot 0
    APSStrictParseArguments {statusCallback dataFile outputFile plot}

    if ![file exists $dataFile] {
        return -code error "APSProcessDCPSMagTempResponse: $dataFile"
    }

    if [catch {exec sddsquery $dataFile -sddsOutput -column \
                 | sddsprocess -pipe -match=column,Name=*MagTempAI \
                 | sdds2stream -pipe -column=Name} MagTempList] {
        return -code error "APSProcessDCPSMagTempResponse: $MagTempList"
    }
    if [llength $MagTempList]==0 {
        eval $statusCallback {"No magnet temperatures measured---no magnet temperature response processing done."}
        return
    }

    regsub -all MagTempAI $MagTempList CurrentAO AOList 
    eval $statusCallback {"[llength $MagTempList] calibration measurements found."}
    set processOptions ""
    set index 0
    foreach item $MagTempList {
        lappend processOptions -process=$item,slope,${item}Slope,functionOf=[lindex $AOList $index]
        lappend processOptions -process=$item,Intercept,${item}Intercept,functionOf=[lindex $AOList $index]
        lappend processOptions -process=$item,lfsd,${item}LFSD,functionOf=[lindex $AOList $index]
        incr index
    }
    if [catch {eval exec sddsprocess $dataFile -pipe=out \
                 $processOptions -nowarning \
                 | sddscollapse -pipe \
                 | sddscollect -pipe \
                 -collect=suffix=Slope -collect=suffix=Intercept \
                 -collect=suffix=LFSD \
                 | sddsprocess -pipe=in $outputFile \
                 -edit=column,PSName,Rootname,%/:MagTempAI//} result] {
        return -code error "APSProcessDCPSMagTempResponse: $result"
    }
}

proc APSSearchAssociateListForValue {args} {
    APSParseArguments {value list notvalue}
    if [info exists value] {
        set match {}
        set i 0
        set n [llength $list]
        while {[expr $i < $n]} {
            if {$value == [lindex $list [expr $i + 1]]} {
                set match [lappend match [lindex $list $i]]
            }
            set i [expr $i + 2]
        }
        return $match
    }
    if [info exists notvalue] {
        set match {}
        set i 0
        set n [llength $list]
        while {[expr $i < $n]} {
            if {$notvalue != [lindex $list [expr $i + 1]]} {
                set match [lappend match [lindex $list $i]]
            }
            set i [expr $i + 2]
        }
        return $match
    }

}

##### Main application #####

APSApplication . -name APSCalibrateDCPS:${systemDescription}${optionDescription} -version $CVSRevisionAuthor \
  -overview "This tool allows calibration of DC power supplies for $system, in the sense of adjusting IOC constants to make the readback (CurrentAI) as close as possible to the setpoint (CurrentAO) over the full range of the supply."


set status "Working..."
APSScrolledStatus .status -parent .userFrame -textVariable status -width 75 \
  -height 5 -packOption "-fill both -expand true"
update

##### System-selective widgets  #####

switch $system {
    sr {
        APSSRDCPSSelectionFrame .srsel -parent .userFrame
    }
    psts {
        APSPSTestStandSelectionFrame .pstssel -parent .userFrame
    }
}

####################################

set dataCollectionSetpoints 20
set percentOfRange 90
set percentOfMaxStart 10
set fitSDLimit 0.1
set offsetLimit 100
set slopeDeviationLimit 0.01

APSFrameGrid .pile -parent .userFrame -yList {1 2 3 4 5} -bd 0
APSFrameGrid .but -parent .userFrame.pile.3 -xList {l c r} -bd 0
APSFrameGrid .but -parent .userFrame.pile.4 -xList {l c r} -bd 0

##### Common widgets #####

APSLabeledEntry .points -parent .userFrame.pile.2 \
  -label "Data collection setpoints: " \
  -textVariable dataCollectionSetpoints \
  -contextHelp "Enter the number of setpoint values to use for calibration."
APSLabeledEntry .percent -parent .userFrame.pile.2 \
  -label "Percent of range to use: " \
  -textVariable percentOfRange \
  -contextHelp "Enter the percentage of the power supply range to use for the calibration.  Using a value too close to 100% will trip some supplies."
APSLabeledEntry .percentStart -parent .userFrame.pile.2 \
  -label "Percent of max to start at: " \
  -textVariable percentOfMaxStart \
  -contextHelp "Applies to unipolar supplies only.  Enter the percentage of the power supply limit to use for starting current in the calibration.  Using 0% will start the supply at zero, which may give poor results due to poor low current regulation."
APSLabeledEntry .fitLimit -parent .userFrame.pile.2 \
  -label "Limit on fit standard deviation: " \
  -textVariable fitSDLimit -contextHelp "Enter the limit (in Amps) of the standard deviation of the fit.  For supplies with SD's above this limit, no installation is done.  In addition, supplies that exceed this are not included in graphs."
APSLabeledEntry .offsetLimit -parent .userFrame.pile.2 \
  -label "Limit on offset (A): " \
  -textVariable offsetLimit \
  -contextHelp "Enter the limit (in Amps) on the offset (AOFF) for data that will be installed. In addition, supplies that exceed this are not included in the graphs."
APSLabeledEntry .slopeDeviationLimit -parent .userFrame.pile.2 \
  -label "Limit on slope deviation from unity: " \
  -textVariable slopeDeviationLimit \
  -contextHelp "Enter the limit on the slope error (|1-ASLO|) for data that will be installed. In addition, supplies that exceed this are not included in the graphs."

APSSecureButton .run -parent .userFrame.pile.3.but.l -text "Run calibration" \
  -command { MakeCalibrationRun } \
  -allowedUsers $allowedUsers -allowedSubnets $allowedSubnets \
  -contextHelp "Runs calibration for the chosen power supplies." \
  -highlightColor red -highlight 1
APSButton .process -parent .userFrame.pile.4.but.l \
  -text "Process/display calibration" \
  -command {ProcessCalibrationChoice -system $system -SDLimit $fitSDLimit \
              -offsetLimit $offsetLimit -slopeDeviationLimit $slopeDeviationLimit      -PSList [APSReturnSelectedDCPS -system $system]} \
  -contextHelp "Process and/or display calibration data previously acquired."

APSButton .displayChosen -parent .userFrame.pile.5 \
  -text "Display chosen details" \
  -command {DisplayChosenDetails} -packOption "-side left" \
  -contextHelp "Displays details from calibration scan on the power supplies selected above. \n\nWARNING: if you choose many supplies, this will take a very long time!"

APSCheckButtonFrame .displayCheck -parent .userFrame.pile.5 \
  -label "Display:" -relief flat \
  -orientation horizontal -packOption "-side left" \
  -buttonList {"RVAL"} -variableList {"showRaw"} \
  -contextHelp "Push this button to display scaling for .RVAL fields instead of Amps."
APSButton .displayWorst -parent .userFrame.pile.5 \
  -text "Display worst\nsupplies' details" \
  -command {DisplayChosenDetails -showWorst 1 -number $displayWorstNumber} \
  -packOption "-side left" -contextHelp \
  "Displays details for the worst N supplies, where N is the number in the box at right. \
         WARNING: if you choose many supplies, this will take a very long time!"
set displayWorstNumber 10
APSLabeledEntry .nDisplayWorst -parent .userFrame.pile.5 \
  -label "Display worst #: " \
  -packOption "-side left" -textVariable displayWorstNumber -width 4 \
  -contextHelp "Enter the number of worst supplies to display."

##### System-selective widgets #####

switch $system {

    sr {
        APSButton .all -parent .userFrame.pile.1 -text "Select all" -command \
          "APSSetVariableListToValue 1 $allSelectionVars" -contextHelp \
          "Select all power supplies in all sectors."
        APSButton .none -parent .userFrame.pile.1 -text "Select none" -command \
          "APSSetVariableListToValue 0 $allSelectionVars" -contextHelp \
          "Deselect all power supplies in all sectors."
        APSButton .show -parent .userFrame.pile.1 -text "Show selections" \
          -command {APSSetVarAndUpdate status [join [APSReturnSelectedDCPS -system $system] ,]} \
          -contextHelp "Shows which power supplies have been selected for calibration."
        APSSecureButton .install -parent .userFrame.pile.3.but.r -text "Install calibration" \
          -packOption "-side right" \
          -command {InstallCalibrationChoice -system $system -SDLimit $fitSDLimit \
                      -offsetLimit $offsetLimit -slopeDeviationLimit $slopeDeviationLimit} \
          -allowedUsers $allowedUsers -allowedSubnets $allowedSubnets \
          -contextHelp "Install calibration data previously acquired and processed." \
          -highlightColor red -highlight 1
        APSSecureButton .newConverter -parent .userFrame.pile.4.but.r -text "Converter replacement" \
          -packOption "-side right" \
          -command {SRInstallNewCalibration -offsetLimit $offsetLimit \
                      -slopeDeviationLimit $slopeDeviationLimit \
                      -magnetList [APSReturnSelectedDCPS -system $system] } \
          -allowedUsers $allowedUsers -allowedSubnets $allowedSubnets \
          -contextHelp "Install calibration data for a replacement converter, \
          either from previously acquired and processed on the power supply test \
          stand, or from ASLO and AOFF values." \
          -highlightColor red -highlight 1
    }

    psts {
        APSButton .displayHistory -parent .userFrame.pile.4.but.r -text "All calibrations history" \
          -command { DisplayPSTSCalibrationHistory -unique 0} -packOption "-side right" \
          -contextHelp "Display information about all calibration records for $system"
        APSButton .displayUniqueHistory -parent .userFrame.pile.3.but.r -text "Most recent calibrations" \
          -command { DisplayPSTSCalibrationHistory -unique 1} -packOption "-side right" \
          -contextHelp "Display information about most recent calibration record for each converter"
    }
}

######################################

APSSetVarAndUpdate status "Ready."

