#!/usr/bin/env python
#################################################################################
#                                                                               #
#   PixArt Data Store and Parse Module                                          #
#   2012 Andrew Beckett - drasnor@tamu.edu                                      #
#                                                                               #
#   This program parses the sample data logs generated by the PixArt logging    #
#   firmwares and stores the resulting data in a dictionary object.             #
#                                                                               #
# Permission is hereby granted, free of charge, to any person obtaining a copy  #
# of this software and associated documentation files (the "Software"), to deal #
# in the Software without restriction, including without limitation the rights  #
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell     #
# copies of the Software, and to permit persons to whom the Software is         #
# furnished to do so, subject to the following conditions:                      #
#                                                                               #
# The above copyright notice and this permission notice shall be included in all#
# copies or substantial portions of the Software.                               #
#                                                                               #
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR    #
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,      #
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE   #
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER        #
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #
# SOFTWARE.                                                                     #
#################################################################################

# Modules
import struct

# Format binary data markers
sessionMarker = str(bytearray([0x7B,0x98,0xA0 ])) # this binary sequence denotes the beginning of a data set
runMarker = str(bytearray([0xA4,0xCB,0x80])) # denotes the beginning of a 15-sample sequence
endMarker = str(bytearray([0xDB,0xBA,0xCD])) # denotes the end of a 15-sample sequence

# Packet format constants, size and resolution in bytes
samplesPerRun = 15
timestampSize = 4
xRes = 2
yRes = 2

# class to hold all the data
class experiment(object):
    # the constructor
    def __init__(self,rawStream):
        self.sessions = [] # initialize sessions list to null
        sessionStreams = rawStream.split(sessionMarker) # partition into multiple sessions
        sessionStreams = filter(None,sessionStreams) # remove empty sessions
        for i in xrange(len(sessionStreams)): # for each sessions in the data stream
            self.sessions.append(session(sessionStreams[i])) # parse each session into runs

# class to hold sample data sessions
class session(object):
    # default constructor
    def __init__(self,sessionStream):
        self.runs = [] # initialize runs list to null
        self.blobs = dict() # initialize blobs dict to null
        runStreams = sessionStream.split(runMarker) # partition into multiple runs
        runStreams = filter(None,runStreams) # remove null sessions
        for i in xrange(len(runStreams)): # for each run in the session stream
            if endMarker in runStreams[i]: # if the run has a valid end-of-run marker
                self.runs.append(runStreams[i].rstrip(endMarker)) # append it to the runs list and strip the marker
                self.parseRun(self.runs[i])
    # run parser
    def parseRun(self,run):
        cursor = 0
        for i in xrange(samplesPerRun): # for each sample in a run
            thisTimestamp = struct.unpack("<L",run[cursor:cursor+timestampSize])[0] # unpack the long timestamp
            cursor += timestampSize # move cursor to the next position
            self.blobs[thisTimestamp] = dict() # initialize new dict at this timestamp
            thisBlobMask = struct.unpack("<B",run[cursor])[0] # unpack the blob mask
            cursor += 1 # move cursor to the next position
            self.blobs[thisTimestamp]["blobMask"] = thisBlobMask # store the blob mask
            if thisBlobMask & 0x01: # blob 0 active
                thisBlobX = struct.unpack("<H",run[cursor:cursor+xRes])[0] # unpack X coordinate
                cursor += xRes # move cursor to the next position
                thisBlobY = struct.unpack("<H",run[cursor:cursor+yRes])[0] # unpack Y coordinate
                cursor += yRes # move cursor to the next position
                self.putBlob(thisTimestamp,"Blob0",thisBlobX,thisBlobY) # store the blob
            if thisBlobMask & 0x02:
                thisBlobX = struct.unpack("<H",run[cursor:cursor+xRes])[0] # unpack X coordinate
                cursor += xRes # move cursor to the next position
                thisBlobY = struct.unpack("<H",run[cursor:cursor+yRes])[0] # unpack Y coordinate
                cursor += yRes # move cursor to the next position
                self.putBlob(thisTimestamp,"Blob1",thisBlobX,thisBlobY) # store the blob
            if thisBlobMask & 0x04:
                thisBlobX = struct.unpack("<H",run[cursor:cursor+xRes])[0] # unpack X coordinate
                cursor += xRes # move cursor to the next position
                thisBlobY = struct.unpack("<H",run[cursor:cursor+yRes])[0] # unpack Y coordinate
                cursor += yRes # move cursor to the next position
                self.putBlob(thisTimestamp,"Blob2",thisBlobX,thisBlobY) # store the blob
            if thisBlobMask & 0x08:
                thisBlobX = struct.unpack("<H",run[cursor:cursor+xRes])[0] # unpack X coordinate
                cursor += xRes # move cursor to the next position
                thisBlobY = struct.unpack("<H",run[cursor:cursor+yRes])[0] # unpack Y coordinate
                cursor += yRes # move cursor to the next position
                self.putBlob(thisTimestamp,"Blob3",thisBlobX,thisBlobY) # store the blob
    # blob mutator
    def putBlob(self,time,blob,x,y):
        self.blobs[time][blob] = dict() # initialize this blob dict
        self.blobs[time][blob]['X'] = x # store this blob x coordinate
        self.blobs[time][blob]['Y'] = y # store this blob y coordinate
