############################################################
# Rolis HK interpreter/extractor
############################################################
# $Id: BrowseRolisHK.tcl,v 1.3 2006-11-17 16:34:46+01 rolf Exp rolf $
#############################################################################
# Different states of the HK interpreter
# ComDPU (DEB0):
#   32 words/recd
# Rolis  (BEEF):
#   64 words/rec
# Intermediate:
#   ComDPU <-> Rolis
# OFF
#   Rolis is switched off
############################################################
# Houskeeping data objects/variables:
# (variable names == pointers
# 1. Frame#id()
#       (^HK)   - List of HK variables (pointers)
#       (^Prev) - Pointer to previous Frame
#       (^Next) - Pointer to next Frame
#       (Hdr.*) - Frame header fields
#       (Unit)  - Device name (Rolis)
#       (Data)  - Raw frame data
# 2. HK#id()
#       (^Frame)    - Pointer to
#       (^Prev)     - Pointer to previous HK
#       (^Next)     - Pointer to next HK
#       (Type)      - HK type: rolis/comdpu/unknown
#       (Dump)      - Hex dump of HK data: all types
#       (Ofs)       - Frame position of HK record in words (informal)
#       (Size)      - Size of HK record in words (informal)
#   (AnalogHK)      - Sorted {name value} pairs
#   (SoftHK)    - Sorted {name value} pairs
#   (Valid.AnalogHK)- 1=ADC data probably valid
#   (Valid.SoftHK)  - 1=SoftHK data probably valid
############################################################

namespace eval browse::hk {
    variable FR     ;# Managment of existing fr#n variables
    variable HK     ;# Managment of existing hk#n variables

    set FR(Names)   {}  ;# List of existing fr#n variables
    set FR(Count)   0   ;# Counter of existing fr#n variables
    set HK(Names)   {}  ;# List of existing hk#n variables
    set HK(Count)   0   ;# Counter of existing hk#n variables

# HK-Type Descriptor
#       channel     format      expr (±3 Volt = ±8192)
   set HkChannels {
        TRD         "%+3.2f °C"  "100.0/0.75*3*$u/8192.0-273.15"
        TSC1        "%+3.2f °C"  "100.0*$u/8192.0-50.0"
        TSC2        "%+3.2f °C"  "100.0*$u/8192.0-50.0"
        TSC3        "%+3.2f °C"  "100.0*$u/8192.0-50.0"
        TSC4        "%+3.2f °C"  "100.0*$u/8192.0-50.0"
        TSC5        "%+3.2f °C"  "100.0*$u/8192.0-50.0"
        TSC6        "%+3.2f °C"  "100.0*$u/8192.0-50.0"
        TSC8        "%+3.2f °C"  "100.0*$u/8192.0-50.0"
        TSC9        "%+3.2f °C"  "100.0*$u/8192.0-50.0"
        TSC10       "%+3.2f °C"  "100.0*$u/8192.0-50.0"
        TSC11       "%+3.2f °C"  "100.0*$u/8192.0-50.0"
        U15P_D      "%+6.3f  V"  "(1+40.2/8.25)*3*$u/8192.0"
        U5N_D       "%+6.3f  V"  "2.0*3*$u/8192.0"
        U5P_D       "%+6.3f  V"  "2.0*3*$u/8192.0"
        HKI+5.2V    "%+6.3f  A"  "1.2*0.5*2.0*3*$u/8192.0"
        U5P_A       "%+6.3f  V"  "2.03*3*$u/8192.0"
    }
}

###################################################
# Formatted HK channels
###################################################
proc browse::hk::analog_hk_rec {data} {
    variable HkChannels

    binary scan $data s16 intList
    foreach {channel format expr} $HkChannels u $intList {
        lappend result $channel  [format $format [expr $expr]]
    }
    set result
}
proc browse::hk::analog_hk { data } {
    binary scan $data @0a32@128a32 data1 data2
    set Hk1 [analog_hk_rec $data1]
    set Hk2 [analog_hk_rec $data2]

    foreach {name1 val1} $Hk1 {name2 val2} $Hk2 {
        append result [format "%-8s  %10s  %10s\n" $name1 $val1 $val2]
    }
    return $result
}

###################################################
# Formatted HK data listing for text output
###################################################
proc browse::hk::list_hk { data {fmt 0} } {
    set result [string trim [interpret_hk $data] ]
    return "\n$result"
}

############################################################
# Extract 3 types of HK data
#   Rolis:  16 ADC channels, 32 words Rolis, 16 words Civa
#   ComDPU: 16 ADC channels, 16 words ComDPU-SW
#   Unknown: dump data
############################################################
proc browse::hk::get_time {hi lo} {
    set msec [expr {($hi<<16) + $lo}] ;# 1 msec resolution
    set sec  [expr {$msec / 1000}]
    set msec [expr {$msec % 1000}]
    set day  [expr {$sec  / 86400}]
    set sec  [expr {$sec  % 86400}]

    set time [clock format $sec -format "%H:%M:%S" -gmt 1]
    format "%03d %s,%03d" $day $time $msec
}
proc browse::hk::get_obt {lo mid {hi 0}} {
    set msec [expr {($lo & 0x1F)*1000/32}]  ;# 31.25 sec resolution
    set sec  [expr {($hi<<27) + ($mid<<11) + ($lo>>5)}]
    set day  [expr {$sec  / 86400}]
    set sec  [expr {$sec  % 86400}]

    set time [clock format $sec -format "%H:%M:%S" -gmt 1]
    format "%03d %s,%03d" $day $time $msec
}
proc browse::hk::get_obt_numeric {lo mid {hi 0}} {
    set msec [expr {($lo & 0x1F)*1000/32}]  ;# 31.25 sec resolution
    set sec  [expr {($hi<<27) + ($mid<<11) + ($lo>>5)}]

    expr {double($sec) + double($msec)/1000}
}


############################################################
# Extract UNKNOWN HK data (dump only)
############################################################
proc browse::hk::get_unknown {data} {
    return {}
}

############################################################
# Extract COMMON-DPU HK data
############################################################
proc browse::hk::get_comdpu {data} {
    set result {}

    binary scan $data @32s16 integers
    foreach x $integers {
        lappend values [expr {$x & 0xFFFF}]
    }

    # values 0..7
    foreach {v0 v1 v2 v3 v4 v5 v6 v7} [lrange $values 0 7] {}
    lappend result Ident       [format "0x%04X" $v0]
    lappend result Time        [get_time $v1 $v2]
    lappend result CdmsOBT     [get_obt $v4 $v3]
    lappend result StatMsg     $v5
    lappend result CmdMsg      $v6
    lappend result DatMsg      $v7

    # values 8..15
    foreach {v0 v1 v2 v3 v4 v5 v6 v7} [lrange $values 8 15] {}
    lappend result SavMsg      $v0
    lappend result DebMsg      $v1
    lappend result ErrCount    $v2
    lappend result FileStat    $v3
    lappend result FilePtr     $v4
    lappend result FileCount   $v5
    lappend result Free        [format "0x%04X 0x%04X" $v6 $v7]
}

############################################################
# Extract ROLIS-DPU HK data
############################################################
proc browse::hk::get_rolis {data} {
    set result {}

    binary scan $data @32s48 integers
    foreach x $integers {
        lappend values [expr {$x & 0xFFFF}]
    }
    # values 0..7
    foreach {v0 v1 v2 v3 v4 v5 v6 v7} [lrange $values 0 7] {}
    lappend result Ident       [format "0x%04X" $v0]
    lappend result Time        [get_time $v1 $v2]
    lappend result CdmsOBT     [get_obt $v5 $v4 [expr {$v3 & 0xFF}] ]
    lappend result CdmsMode    [format "0x%02X" [expr {$v3 >> 8}] ]
    lappend result CdmsSST     [format "0x%04X" $v6]
    lappend result StatMsg     $v7

    # values 8..15
    foreach {v0 v1 v2 v3 v4 v5 v6 v7} [lrange $values 8 15] {}
    lappend result TcmdMsg     $v0
    lappend result DataMsg     $v1
    lappend result SavMsg      $v2
    lappend result ErrSR       $v3
    lappend result ErrTcmd     [expr {$v4 >> 8}]
    lappend result RolisTcmd   [expr {$v4 & 0xFF}]
    lappend result CivaTcmd    $v5
    lappend result CivaMsg     $v6
    lappend result CivaShift   [expr {$v7 >> 12}]
    lappend result CivaClock   [expr {($v7 >> 8) & 0x0F}]
    lappend result CivaErr     [expr {$v7 & 0xFF}]

    # values 16..23
    foreach {v0 v1 v2 v3 v4 v5 v6 v7} [lrange $values 16 23] {}
    lappend result Config      [format "0x%04X" $v0]
    lappend result Flags0      [format "0x%04X" $v1]
    lappend result IflPos      [expr {$v2 >> 8}]
    lappend result PowerStat   [format "0x%02X" [expr {$v2 & 0xFF}]]
    set frPage [expr {$v3 >> 8}]
    if {$frPage > 127} {incr frPage -256}
    lappend result FramePage   $frPage
    lappend result ZtPage      [expr {$v3 & 0xFF}]
    lappend result frStatRolis [expr {$v4 >> 8}]
    lappend result frStatCiva  [expr {$v4 & 0xFF}]
    lappend result frCntRolis  $v5
    lappend result frCntCiva   $v6
    lappend result CifFrames   $v7

    # values 24..31
    foreach {v0 v1 v2 v3 v4 v5 v6 v7} [lrange $values 24 31] {}
    lappend result TaskFlags   [format "0x%02X" [expr {$v0 >> 8}]]
    lappend result TaskActive  [expr {$v0 & 0xFF}]
    lappend result TaskSPR     [format "0x%04X 0x%04X 0x%04X 0x%04X" $v1 $v2 $v3 $v4]
    lappend result CifMsgBufOvr [expr {$v5 >> 8}]
    lappend result CifTcmdErr  [expr {$v5 & 0xFF}]
    lappend result CifTcmdCount $v6
    lappend result Free        [format "0x%04X" $v7]
    # values 32..39
    foreach {v0 v1 v2 v3 v4 v5 v6 v7} [lrange $values 32 39] {}
    lappend result Civa0       [format "0x%04X 0x%04X 0x%04X 0x%04X" $v0 $v1 $v2 $v3]
    lappend result Civa1       [format "0x%04X 0x%04X 0x%04X 0x%04X" $v4 $v5 $v6 $v7]

    # values 40..47
    foreach {v0 v1 v2 v3 v4 v5 v6 v7} [lrange $values 40 47] {}
    lappend result Civa2       [format "0x%04X 0x%04X 0x%04X 0x%04X" $v0 $v1 $v2 $v3]
    lappend result Civa3       [format "0x%04X 0x%04X 0x%04X 0x%04X" $v4 $v5 $v6 $v7]

    set result
}

############################################################
# Simplified version of getting Software-HK values
# - without checking prev./next HK frames
# - but you can scroll up & down
# HK frame = 128 words
# - Interpret the HK frame ONLY if ALL records are ROLIS or ALL are ComDPU
# - ROLIS:  data[16]=data[80]=BEEF and data[48,112]!=DEB0
# - ComDPU: data[16]=data[48]=data[80]=data[112]=DEB0
############################################################
proc browse::hk::soft_hk { data } {

    binary scan $data @32s1@96s1@160s1@224s1 id1 id2 id3 id4
    ::browse::unsigned id1 id2 id3 id4

    if { ($id1==0xBEEF) && ($id3==$id1) && ($id2!=0xDEB0) && ($id4!=0xDEB0) } {
        # Rolis-DPU HK data, 2x64 words
        binary scan $data a128a128 data1 data2
        set rec1 [browse::hk::get_rolis $data1]
        set rec2 [browse::hk::get_rolis $data2]
        foreach {n1 v1} $rec1 {n2 v2} $rec2 {
            append result [format "%-15s %28s %28s\n" $n1 $v1 $v2]
        }
    } elseif { ($id1==0xDEB0) && ($id2==$id1) && ($id3==$id1) && ($id4==$id1) } {
        # Common-DPU HK data, 4x32 words
        binary scan $data a64a64a64a64 data1 data2 data3 data4
        set rec1 [browse::hk::get_comdpu $data1]
        set rec2 [browse::hk::get_comdpu $data2]
        set rec3 [browse::hk::get_comdpu $data3]
        set rec4 [browse::hk::get_comdpu $data4]
        foreach {n1 v1} $rec1 {n2 v2} $rec2 {n3 v3} $rec3 {n4 v4} $rec4 {
            append result [format "%-10s %16s %16s %16s %16s\n" $n1 $v1 $v2 $v3 $v4]
        }
    } else {
        set result "< Cannot display mixed ComDPU/Rolis or unknown HK data >"
    }
    set result
}

###############################################################
# Extract HK data into a linked structure of arrays
# Afterwards these HK-arrays are cross-checked for consistancy
###############################################################

############################################################
# Extract all HK frames from a file
############################################################
proc browse::hk::new_frame {} {
    variable FR

    set prev [lindex $FR(Names) end]
    set name fr#$FR(Count)
    variable $name

    lappend FR(Names) $name
    incr FR(Count)

    array set $name [list ^Prev $prev ^Next {} ]
    if { $prev != "" } {
        variable $prev
        array set $prev [list ^Next $name]
    }
    return $name
}

proc browse::hk::extract_frame {frame} {
    variable FR

    browse::cdms_frame $frame X
    if { [string equal -nocase $X(Unit) ROLIS] && [string equal -nocase $X(Type) HK] } {
        set name [new_frame]
        variable $name
        array set $name [array get X]

        return $name
    }
    return {}
}

############################################################
# Create a new HK array (linked records)
############################################################
proc browse::hk::new_hk {} {
    variable HK

    set prev [lindex $HK(Names) end]
    set name hk#$HK(Count)
    variable $name

    lappend HK(Names) $name
    incr HK(Count)

    array set $name [list ^Prev $prev ^Next {} ]
    if { $prev != "" } {
        variable $prev
        array set $prev [list ^Next $name]
    }
    return $name
}
proc browse::hk::new_hk_data {type data frName ofs} {
    set name [new_hk]
    variable $name

    set ${name}(Type)   $type
    set ${name}(Dump)   [::browse::dump $data]
    set ${name}(^Frame) $frName
    set ${name}(Ofs)    $ofs
    set ${name}(Size)   [expr {[string length $data]/2} ]

    return $name
}

############################################################
# Extract HK data into a new arrays
# NOTE: The ofs parameter is informal only
############################################################
proc browse::hk::new_comdpu {data frName ofs} {
    set hk [new_hk_data comdpu $data $frName $ofs]
    variable $hk
    set ${hk}(AnalogHK) [browse::hk::analog_hk_rec $data]
    set ${hk}(SoftHK)   [browse::hk::get_comdpu $data]
}
proc browse::hk::new_rolis {data frName ofs} {
    set hk [new_hk_data rolis $data $frName $ofs]
    variable $hk
    set ${hk}(AnalogHK) [browse::hk::analog_hk_rec $data]
    set ${hk}(SoftHK)   [browse::hk::get_rolis $data]
}
proc browse::hk::new_unknown {data frName ofs} {
    set hk [new_hk_data unknown $data $frName $ofs]
    variable $hk
    set ${hk}(SoftHK)   [browse::hk::get_unknown $data]
}

############################################################
# Extract HK data from one HK record
############################################################
proc browse::hk::next_hk_record { data frName ofs } {
    #
    # The ofs parameter is informal, to be stored in hk#n array
    # Data block = 64 words = 128 bytes
    # Get 2 ID's DEB0/BEEF
    # id1=data[32] == BEEF(Rolis) / DEB0(ComDPU) ?
    # id2=data[96] == DEB0(ComDPU) ?
    #
    binary scan $data @32s1@96s1 id1 id2
    ::browse::unsigned id1 id2

    # Split data into 2x32 word records
    binary scan $data a64a64 data1 data2

    # check the current ID's for this 64 word block
    if {$id1 == 0xDEB0} {
        if {$id2 == 0xDEB0} {
            # 2 ComDPU blocks 2 x 32 words
            browse::hk::new_comdpu $data1 $frName $ofs
            browse::hk::new_comdpu $data2 $frName [expr {$ofs + 32}]
        } else {
            # 1 ComDPU block 32 words, --> switch to ROLIS-HK
            browse::hk::new_comdpu  $data1 $frName $ofs
            browse::hk::new_unknown $data2 $frName [expr {$ofs + 32}]
        }
    } elseif {$id1 == 0xBEEF} {
        if {$id2 == 0xDEB0} {
            # 1/2 ROLIS block --> switch to ComDPU-HK
            # But get all 128 bytes, we may find useful ROLIS info in the first part
            browse::hk::new_rolis  $data  $frName $ofs
            browse::hk::new_comdpu $data2 $frName [expr {$ofs + 32}]
        } else {
            browse::hk::new_rolis $data $frName $ofs
        }
    } elseif {$id2 == 0xDEB0} {
        # Probably unit was OFF --> switched on ComDPU
        browse::hk::new_unknown $data1 $frName $ofs
        browse::hk::new_comdpu  $data2 $frName [expr {$ofs + 32}]
    } else {
        # Probably unit is OFF or NONSENSE data, dump 64 words
        browse::hk::new_unknown $data $frName $ofs
    }
}
proc browse::hk::next_hk_frame { frName } {
    upvar #0 ::browse::hk::$frName fr

    # Split data into 2x64 word records
    # and interpret them separately
    binary scan $fr(Data) a128a128 data1 data2
    next_hk_record $data1 $frName 0
    next_hk_record $data2 $frName 64
}
proc browse::hk::extract_hk {} {
    variable FR

    foreach frName $FR(Names) {
        next_hk_frame $frName
    }
}

############################################################
# Check HK types
# HK data may be invalid on transition ComDPU <--> Rolis <--> OFF
############################################################
proc browse::hk::check_hk_rec { hkName } {
    variable $hkName

    set prev [set ${hkName}(^Prev)]
    set next [set ${hkName}(^Next)]
    set type [set ${hkName}(Type)]

    set ${hkName}(Valid.AnalogHK)  0    ;# ADC data valid ?
    set ${hkName}(Valid.SoftHK)    0    ;# Soft-HK valid ?

    if { $prev != "" } {
        variable $prev
        set pType [set ${prev}(Type)]
        if { [string equal -nocase $type $pType] } {
            set ${hkName}(Valid.AnalogHK)  1
        }
    }
    if { $next != "" } {
        variable $next
        set nType [set ${next}(Type)]
        if { [string equal -nocase $type $nType] } {
            set ${hkName}(Valid.SoftHK)  1
        }
    }
}
proc browse::hk::check_hk {} {
    variable HK

    foreach hkName $HK(Names) {
        check_hk_rec $hkName
    }
}
############################################################
# Print HK data
############################################################
proc browse::hk::print_hk_rec { hkName } {
    upvar #0 [namespace current]::$hkName HK

    set result [format "%-7s type=%-7s AnalogHK=%d SoftHK=%d   %7s ofs=%-3d size=-3d\n" \
        $hkName $HK(Type) $HK(Valid.AnalogHK) $HK(Valid.SoftHK) $HK(^Frame) $HK(Ofs) $HK(Size) ]
    switch -- $HK(Type) {
        rolis -
        comdpu {
            foreach {name val} $HK(AnalogHK) {
                append analoghk [format "%-12s %s\n" $name $val]
            }
            foreach {name val} $HK(SoftHK) {
                append softhk [format "%-12s %s\n" $name $val]
            }
            append dump "ADC:\n" [browse::indent $analoghk] "SoftHK:\n" [browse::indent $softhk]
        }
        unknown {
            append dump "DUMP:\n" [browse::indent $HK(Dump)]
        }
    }
    append result [browse::indent $dump]
}

proc browse::hk::print_hk_grep { hkName grepList } {
    upvar #0 [namespace current]::$hkName HK

    switch -- $HK(Type) {
        rolis -
        comdpu {
            set result {}
            array set X [concat $HK(AnalogHK) $HK(SoftHK)]
            foreach g $grepList {
                catch { lappend result $X($g) }
            }
            return [join $result "\t"]
        }
    }
}

proc browse::hk::print_hk { chan {grepList {}} } {
    variable HK

    if {[llength $grepList] == 0} {
        foreach hkName $HK(Names) {
            puts $chan [print_hk_rec $hkName]
        }
    } else {
        puts $chan [join $grepList "\t"]
        foreach hkName $HK(Names) {
            set result [print_hk_grep $hkName $grepList]
            if { [string length $result] } {
                puts $chan $result
            }
        }
    }
}

#############################################################################
# browse::hk_file filename proc
#############################################################################
# List the whole CDMS data file
#############################################################################
# Parameters:
#   filename    - name of the file to be browsed
# Returns:
#   result      - formatted frame data (ALL fields) in MULTIPLE lines
#############################################################################
proc browse::hk::doit { args } {
    variable ::browse::BROWSE

    if { [llength $args] == 0 } {
        browse::errorMsg "Hk: valid options are:
    -file filename     - input filename
    -from num          - extract from record number (def=0)
    -to num            - extract to record number (def=end)
    -out filename      - name of output file (def=stdout)
    -grep hk1,hk2,...  - HK channel(s) to be grep'ed"
    }

    # default parameters
    set ARG(From)       0
    set ARG(To)         [expr {0x7FFFFFFF}]
    set ARG(File)       {}
    set ARG(Out)        {}
    set ARG(Grep)       {}

    # parse command parameters
    foreach {opt val} $args {
        set opt [string tolower $opt]
        switch -glob -- $opt {
            -from       { set ARG(From)     $val }
            -to         { set ARG(To)       $val }
            -file       { set ARG(File)     $val }
            -out        { set ARG(Out)      $val }
            -grep       { set ARG(Grep)     $val }
            default { browse::errorMsg "Hk: invalid option $opt" }
        }
    }

    if { [string length $ARG(File)] } {
        set fin [browse::open_infile $ARG(File)]
    } else {
        browse::errorMsg "Hk: no data file specified (-file filename)"
    }
    if { [string length $ARG(Out)] == 0 } {
        set fout stdout
        browse::config_stdfile $fout
    } else {
        #
        # Output is a text-file, not binary !!!
        #
        set fout [browse::open_outfile $ARG(Out)]
        fconfigure $fout -translation auto
    }
    browse::detect_frame_size $fin

    seek $fin [expr {$ARG(From)*$BROWSE(Frame.Size)}]
    set BROWSE(FrameNo) $ARG(From)

    # 1. Read all data into fr#i (1=0..N) variables
    #
    while { 1 } {
        set frame [read $fin $BROWSE(Frame.Size)]
        if { [string length $frame] != $BROWSE(Frame.Size) } {
            break
        }
        extract_frame $frame
        incr BROWSE(FrameNo)

        if { $BROWSE(FrameNo) > $ARG(To) } {
            break
        }
    }
    close $fin

    # 2. Extract HK data from all frames
    extract_hk
    # 3. Check HK data for ComDPU <--> Rolis changes
    check_hk
    # 4. Print all HK records
    print_hk $fout [split $ARG(Grep) ","]

}
