#############################################################################
# CDMS: Interface to CDMS-simulator
#   (c) 2000: Rolf Schrödter, DLR, Institut für Planetenerkundung
#############################################################################
# $Id: BrowseCDMS.tcl,v 1.3 2006-11-17 16:34:46+01 rolf Exp rolf $
#############################################################################
# BrowseCDMS.tcl :  Extract CDMS frames
#############################################################################


#############################################################################
# BrowseCDMS filles the following data structure into the specified variable:
#   X(Hdr.Synchro)
#   X(Hdr.UnitPID)
#   X(Hdr.Category)
#   X(Unit)
#   X(Type)
#   X(Hdr.PackSeq)
#   X(Hdr.PackLen)
#   X(Hdr.OBT)
#   X(Hdr.DataFieldHdr)
#   X(Hdr.StartFmt)     ;# HK data only
#   X(Hdr.ScanPeriod)   ;# HK data only
#   X(Hdr.ProcParm)     ;# Science data only
#   X(Data)
#   X(Hdr.DECW)
#   X(Hdr.PECW)         ;# Only if BROWSE(Frame.Size) >= 282
#############################################################################


#######################################################
# Open & Configure files
#######################################################
proc browse::open_infile { fname } {
    variable BROWSE
    set chan [open $fname r]
    fconfigure $chan -translation binary -buffering full -buffersize $BROWSE(BufSize.In)
    return $chan
}
proc browse::open_outfile { fname } {
    variable BROWSE
    set chan [open $fname w]
    fconfigure $chan -translation binary -buffering full -buffersize $BROWSE(BufSize.Out)
    return $chan
}
proc browse::config_stdfile { chan } {
    variable BROWSE
    fconfigure $chan -buffering full -buffersize $BROWSE(BufSize.Out)
    return $chan
}

#######################################################
# Generic data type handlers
#######################################################

proc browse::byte { args } {
    set result ""
    foreach var $args {
        upvar $var X
        set X [expr {$X & 0xFF} ]        ;# 8 bit unsigned
        lappend result $X
    }
    return $result
}
proc browse::unsigned { args } {
    set result ""
    foreach var $args {
        upvar $var X
        set X [expr {$X & 0xFFFF}]     ;# 16 bit unsigned
        lappend result $X
    }
    return $result
}
proc browse::unsigned32 { args } {
    set result ""
    foreach var $args {
        upvar $var X
        set X [expr {$X & 0xFFFFFFFF}]     ;# 16 bit unsigned
        lappend result $X
    }
    return $result
}

#######################################################
# Create Hex dump from binary data
# Format:
#   0123 4567 89ab cdef 0123 4567 89ab cdef     ................
#######################################################

proc browse::dump16 { data } {    ;# data = binary string
    set hex ""
    binary scan $data s* int_list   ;# max 16 Data Words (little endian)
    foreach i $int_list {
        browse::unsigned i
        append hex [format "%04X " $i]  ;# format data in variable 'hex'
    }
    set data [binary format S* $int_list]       ;# big endian

############# regsub STOPS AT NULL CHARACTER IN $data ################
    set data [join [split $data \x00] .]        ;# replace NULL characters by "." -> regsub can handle $data
    regsub -all "\[^\x20-\x7F\]" $data "." str  ;# replace other non-printable characters by "."

    return [format "%-45s%s" $hex $str]
}
proc browse::dump { data } {
    set result ""
    while { [string length $data] > 16 } {
        binary scan $data a16a* x data
        append result "[browse::dump16 $x]\n"
    }
    binary scan $data a* x
    append result [browse::dump16 $x]

    return $result
}
proc browse::indent {txt {chars \t}} {
    # remove eventually trailing newline
    regsub "\n$" $txt "" txt
    # indent each line
    foreach line [split $txt \n] {
        append result $chars $line\n
    }
    set result
}

#######################################################
# get SSADR from UnitPID
# get Unit name from SSADR
#######################################################
proc browse::pid2ssadr {pid} {
    expr { ($pid > 109) ? ($pid - 109) : ($pid - 85) }
}
proc browse::ssadr2name {unit} {
    variable BROWSE
    if { [info exists BROWSE(Unit.$unit)] } {
        return $BROWSE(Unit.$unit)
    } else {
        return "Unknown($unit)"
    }
}

#######################################################
# Calculate checksum
#######################################################

proc browse::checksum {data} {
    binary scan $data s* words
    set sum 0
    foreach x $words {
        incr sum [expr {$x & 0xFFFF}]
    }
    return [expr {$sum & 0xFFFF}]
}

#######################################################
# browse::cdms_frame frame var
#######################################################
# CDMS record interpretation
#
# Parameters
#   frame       - Binary CDMS frame 280/282 bytes
#   var         - Variable to be filled with CDMS frame fields
#                   var is cleared at the beginning
# Retunrs
#   <noting>
#
# Errors
#   various errors on data interpretation
#######################################################
proc browse::cdms_frame { frame var } {
    variable BROWSE
    upvar $var X

# Clear variable first
#   Oooooooh this would disable traces ......
#    catch {unset X}

# Check frame size
    if { [string length $frame] != $BROWSE(Frame.Size) } {
        error "BROWSE: invalid frame size"
    }

    switch -- $BROWSE(Frame.Size) {
        256 {       # 128 hk/data words
            binary scan $frame a256 X(Data)  ;# 128 Data Words
            return
        }
        280 -
        282 { ;# continue operation
        }
        default {
            error "BROWSE: unknown frame size"
        }
    }

# Frame synchro pattern
    binary scan $frame @0i1 x
    browse::unsigned32 x

    if { $x != $BROWSE(Frame.Synchro) } {
        error "BROWSE: invalid frame synchro"
    }
    set X(Hdr.Synchro) [ format "0x%08X" $x ]

# Frame PacketID / UnitPID / Category
    binary scan $frame @4s1 x
    set X(Hdr.UnitPID) [expr {($x & 0x07F0) >> 4}]
    set X(Hdr.Category) [ expr {$x & 0x000F} ]      ;# should be PRIVATE=1100

# Unit name (ROLIS,SESAME,...)
    set unit [browse::pid2ssadr $X(Hdr.UnitPID)]
    set X(Unit) [browse::ssadr2name $unit]

# Frame type HK/DATA
    binary scan $frame @20s1 x                      ;# Format id
    set hkFlag [expr {$x & 0x1000} ]                ;# =0: Science =0x1000: HK

    set X(Type)  [ expr { $hkFlag ? "HK" : "DATA" } ]

# Packet Sequence Control
    binary scan $frame @6s1s1 x1 x2
    set X(Hdr.PackSeq) [ expr {$x1 & 0x3FFF} ]
    set X(Hdr.PackLen) [browse::unsigned x2]

# Data Field Header: 48 bits Onboard Time
    binary scan $frame @10s1s1s1 sec_hi sec_lo frac
    browse::unsigned sec_hi sec_lo frac
    set sec [expr {($sec_hi << 16) | $sec_lo}]
    set msec [expr {$frac * 1000 / 65536}]

    set X(Hdr.OBT.Num) [expr {double($sec) + double($msec)/1000}]

    set day  [expr {$sec / 86400}]
    set sec  [expr {$sec % 86400}]
    set hour [expr {$sec / 3600}]
    set sec  [expr {$sec % 3600}]
    set min  [expr {$sec / 60}]
    set sec  [expr {$sec % 60}]
    set X(Hdr.OBT) [ format "%d %02d:%02d:%02d,%03d" $day $hour $min $sec $msec ]

# Data Field Header: extra bytes
    binary scan $frame @16s1s1 x1 x2      ;# 32 bits ???
    browse::unsigned x1 x2
    set X(Hdr.DataFieldHdr) [ format "0x%04X%04X" $x1 $x2 ]

# Format ID, different for HK/DATA
    binary scan $frame @20s1 x
    browse::unsigned x
    if { $hkFlag } {        ;# Hk only: Start Format Count
        set X(Hdr.StartFmt)   [ expr {($x & 0x8000) >> 8} ]
        set X(Hdr.ScanPeriod) [ expr {($x & 0x6000) >> 13} ]
    } else {
        set X(Hdr.ProcParm) [ format "0x%04X" [expr {$x & 0x0FFF}] ]
    }

# 128 hk/data words
    binary scan $frame @22a256 X(Data)  ;# 128 Data Words

# Calculate frame checksum
    binary scan $frame @4a274 chkdata  ;# 137 Data Words
    set ChkSum [browse::checksum $chkdata]
# Checksum: DECW, PECW
    binary scan $frame @278s1s1 DECW x2
    browse::unsigned DECW x2
    set X(Hdr.DECW) [ format "0x%04X 0x%04X" $DECW $ChkSum ]
    if { $BROWSE(Frame.Size) == 282 } {           ;# some file have PECW, some don't
        set X(Hdr.PECW) [ format "0x%04X" $x2 ]
    }
    set X(ChksumErr) [expr {$DECW != $ChkSum}]
}

#######################################################
# Frame size size detector
#######################################################
proc browse::detect_frame_size { chan } {
    variable BROWSE

    set filePos [tell $chan]    ;# save current file position

    seek $chan 0 end
    set fileSize [tell $chan]   ;# get current file size

    if { $fileSize < 286 } {    ;# special case: 1 record in file
        switch $fileSize {
            256     { set size 256 }
            280     { set size 280 }
            282     { set size 282 }
            default { error "Browse: File is not a valid CDMS file" }
        }
    } else {
        seek $chan 0 start
        set frame [read $chan 286]
        binary scan $frame @0i1@280i1@282i1 sync0 sync280 sync282
        browse::unsigned32 sync0 sync280 sync282

        if { $sync0 != $BROWSE(Frame.Synchro) } {
            if { $fileSize % 256 == 0 } {
                set size 256
            } else {
                error [format "Browse: 1-Unknown CDMS file type (Synchro=%08X sync0=%08X sync280=%08X sync282=%08X)" $BROWSE(Frame.Synchro) $sync0 $sync280 $sync282]
            }
        } elseif { $sync280 == $BROWSE(Frame.Synchro) } {
            set size 280
        } elseif { $sync282 == $BROWSE(Frame.Synchro) } {
            set size 282
        } else {
            error [format "Browse: 2-Unknown CDMS file type (Synchro=%08X sync0=%08X sync280=%08X sync282=%08X)" $BROWSE(Frame.Synchro) $sync0 $sync280 $sync282]
        }
    }
    seek $chan $filePos             ;# restore original file position
    set BROWSE(Frame.Size) $size    ;# return frame size
}
