#!/usr/bin/python
#
# Python-based accelerometer calibration for Mercury H3600 Backpaq project
#
# Copyright 2001 Compaq Computer Corporation.
#
# Use consistent with the GNU GPL is permitted,
# provided that this copyright notice is
# preserved in its entirety in all copies and derived works.
#
# COMPAQ COMPUTER CORPORATION MAKES NO WARRANTIES, EXPRESSED OR IMPLIED,
# AS TO THE USEFULNESS OR CORRECTNESS OF THIS CODE OR ITS
# FITNESS FOR ANY PARTICULAR PURPOSE.
#                   
# Author: Andrew Christian
#         <andrew.christian@compaq.com>
#

import sys
import os
import string
from gtk import *
import GtkExtra

accel_data = "/proc/backpaq/accel"
eeprom_proc_gen = "/proc/backpaq/eeprom"

class accel_data:
    def update(self):
        f = open("/proc/backpaq/accel", "r")
        for item in ( 'xt1', 'xt2', 'yt1', 'yt2',
                      'xoffset', 'xscale', 'yoffset', 'yscale' ):
            setattr(self,item,eval(string.split(f.readline()," : ")[1]))
        f.close()

    def xaccel(self):
        scaled_value = self.xscale * (( self.xt1 * (65536 - self.xoffset) - self.xoffset * self.xt2 ) \
                                      / (self.xt1 + self.xt2)) / ( 0x100000 )
        return scaled_value / 256.0

    def yaccel(self):
        scaled_value = self.yscale * (( self.yt1 * (65536 - self.yoffset) - self.yoffset * self.yt2 ) \
                                      / (self.yt1 + self.yt2)) / ( 0x100000 )
        return scaled_value / 256.0

    def xdata(self):
        return float(self.xt1) / float( self.xt1 + self.xt2 )

    def ydata(self):
        return float(self.yt1) / float( self.yt1 + self.yt2 )
    
    def __init__(self):
        self.update()

def find_calibration( data_list ):
    "Returns the calculated offset and scale for a list of raw data tuples"
    # Look for minimum and maximum values of d1 / (d1 + d2)
    data_list.sort()
    num = int(len(data_list) * .1) + 1
    lbound = reduce( (lambda x,y: x + y), data_list[0:num]) / num
    ubound = reduce( (lambda x,y: x + y), data_list[-1-num:-1]) / num
    # Calculate the offset and scale
    offset = ( lbound + ubound ) / 2
    scale =  0.125 / (ubound - offset)
    hoffset = int(offset * 65536)
    hscale = int(scale * 32768)
    return hoffset, hscale

eeprom_proc_base = "/proc/sys/backpaq/eeprom/accel/"

def store_eeprom(name, value):
    "Poke into the proc file system"
    dest = eeprom_proc_base + name
    if ( os.access(dest, os.W_OK)):
        f = open(dest,"w")
        f.write("%d" % value)
        f.close()
    else:
        print "Failure to write!\n"

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

def mainfunc(windowMainWidget):
    windowMainWidget.windowMain.connect("delete_event", mainquit)

def drawing_configure_event(widget, event, data ):
    "Callback when the drawing area gets a configure event"
    pass

def draw_circle( drawable, gc, x, y, diameter, filled=FALSE ):
    x1 = x - diameter / 2
    y1 = y - diameter / 2
    draw_arc( drawable, gc, filled, x1, y1, diameter, diameter, 0, 360 * 64 )

def drawing_expose_event(widget, event, win ):
    "Callback when the drawing area gets an expose event"
    drawable = widget.get_window()
    white_gc = widget.get_style().white_gc
    grey_gc  = widget.get_style().bg_gc[STATE_NORMAL]
    black_gc = widget.get_style().black_gc
    max_width  = drawable.width
    max_height = drawable.height
    draw_rectangle(drawable, white_gc, TRUE, 0, 0, max_width, max_height )
    draw_circle( drawable, black_gc, max_width / 2, max_height / 2, max_width * 0.2 )
    draw_circle( drawable, black_gc, max_width / 2, max_height / 2, max_width * 0.8 )
    xpos = win.accel.xaccel()
    ypos = win.accel.yaccel()
    if win.state_active:
        win.stored_x.append(win.accel.xdata())
        win.stored_y.append(win.accel.ydata())

    draw_circle( drawable, black_gc,
                 ((xpos + 1.25) / 2.5) * max_width,
                 ((ypos + 1.25) / 2.5) * max_height,
                 max_width * 0.05, TRUE)

def request_expose_event(widget):
    drawable = widget.get_window()
    max_width  = drawable.width
    max_height = drawable.height
    widget.draw( (0,0,max_width,max_height ))

def dlg_display_click(widget,mainObj):
    "Called when the dialog button is pushed"
    mainObj.destroy();

def dlg_destroy(widget, mainObj):
    "Called when the dialog is destroyed"
    widget.hide()
    mainquit()

def makeButton( name, mainObj, callback ):
    "Build a single button"
    btn=GtkButton(name)
    btn.set_flags(CAN_FOCUS)
    btn.set_usize(-1, -1)
    btn.connect("clicked", callback, mainObj)
    btn.GTKPY_MAIN = mainObj
    btn.show()
    return btn

def makeLabel(text):
    "Build a single label widget and install it in a table"
    myLabel = GtkLabel()
    myLabel.set_usize(-1, -1)
    myLabel.set_text(text)
    myLabel.set_justify(JUSTIFY_LEFT)
    myLabel.set_alignment(0, 0.5)
    myLabel.set_padding(0, 0)
    myLabel.show()
    return myLabel
    
def displayMessage(title,text):
    dlg = new( GtkDialog,
               type=WINDOW_TOPLEVEL,
               title=title,
               allow_grow=TRUE,
               allow_shrink=FALSE,
               border_width=10 )
    label = makeLabel(text)
    dlg.vbox.pack_start(label,FALSE,FALSE,0)
    btn = makeButton("Okay", dlg, dlg_display_click )
    dlg.action_area.pack_start(btn,FALSE, FALSE, 0)
    dlg.set_modal(TRUE)
    dlg.connect("destroy", dlg_destroy, 0)

    dlg.show()
    mainloop()

def about_callback( widget, win ):
    "About button pushed"
    displayMessage("About",
                   "Accelerometer calibration\n\n"\
                   "Push the 'start' button and\n"\
                   "gently rotate your backpaq in\n"\
                   "the vertical plane so that each\n"\
                   "side spends about the same time\n"\
                   "upright.  Push 'stop' to finish.\n")

def calibrate_callback( widget, win ):
    "Gather data for calibration"
    if win.state_active:
        widget.children()[0].set_text("Start")
        win.state_active = FALSE
        print len(win.stored_x), "data items stored"
        xoffset, xscale = find_calibration(win.stored_x)
        store_eeprom('xoffset', xoffset)
        store_eeprom('xscale', xscale)
        yoffset, yscale = find_calibration(win.stored_y)
        store_eeprom('yoffset', yoffset)
        store_eeprom('yscale', yscale)
    else:
        widget.children()[0].set_text("Stop")
        win.state_active = TRUE
        win.stored_x = []
        win.stored_y = []

def handle_timeout(window):
    window.accel.update()
    request_expose_event(window.drawingArea)
    return TRUE
#    timeout_add(100,handle_timeout,window)

class windowMainWidget:
    def __init__(self):
        self.myWidgets = {}   # Storage for all widgets we need to access
        self.dirty = 0
        
        windowMain=GtkWindow(WINDOW_TOPLEVEL)
        windowMain.set_title("Calibrate")
        windowMain.set_usize(-1, -1)
        windowMain.set_policy(FALSE, FALSE, FALSE)
        windowMain.set_position(WIN_POS_NONE)

        vbox1=GtkVBox()
        windowMain.add(vbox1)
        vbox1.set_usize(-1, -1)
        vbox1.set_homogeneous(FALSE)
        vbox1.set_spacing(0)

        da = GtkDrawingArea()
        vbox1.pack_start(da,FALSE,FALSE,0)
        da.set_usize(150,150)
        da.show()
        da.connect("configure_event", drawing_configure_event, self )
        da.connect("expose_event", drawing_expose_event, self )
        self.drawingArea = da

        hbox1 = GtkHBox()
        vbox1.pack_start(hbox1,FALSE,FALSE,0)
        hbox1.set_usize(-1,-1)
        hbox1.set_homogeneous(TRUE)
        hbox1.set_spacing(0)

        b1 = makeButton("About",self,about_callback)
        hbox1.pack_start(b1)
        b2 = makeButton("Start",self,calibrate_callback)
        hbox1.pack_start(b2)
        
        hbox1.show()
        vbox1.show()
        windowMain.show()
        self.windowMain=windowMain
        self.state_active = FALSE
        self.stored_x = []
        self.stored_y = []

if __name__ == '__main__':
    # Are we actively calibrating?
    if ( os.access(eeprom_proc_gen, os.R_OK)):
        f = open(eeprom_proc_gen, "r")
        line = f.readline()
        f.close()
        if ( string.find(string.lower(line),"okay") >= 0):  # Valid EEPROM
            mainWindow=windowMainWidget()
            mainfunc(mainWindow)
            mainWindow.accel = accel_data()
            timeout_add(100,handle_timeout,mainWindow)
            mainloop()
        else: # Invalid EEPROM - must program first
            displayMessage("Warning!",
                           "Your EEPROM does not appear to \n"\
                           "have been programmed.  Please \n"\
                           "run 'guiprom' to program it and\n"\
                           "try again.")
    else:
        # Invalid world, must request backpaq first
        displayMessage("Warning",
                       "I can't locate a valid Backpaq.\n"\
                       "Exiting program")

