How to store the data? A python Lap Timer

I’m working on a lap timer project. I’ve been recruited by the organiser of my team (I was just a competitor) as last race we just had an excel spreadsheet. While this had the obvious advantage of being able to quickly analyse the results and create pretty graphs (:P), it wasn’t ideal. One day, when i was showing off a flash game of Pong i made, i was recruited. I initially decided that wxPython (through wxGlade as the designer) would be the best way.

Anyway, i’ve figured out how to get the time to show up (a running time, in hours, minutes and seconds) and i have an intellectual theory on how to get it to start when a button is pressed (quite easy, just a function).

The main problem is this: I have no idea how to store the lap times.

  • The race goes for 24 hours
  • There are numerous pit changes, and the drivers do not wish for the pit changes to be counted onto their times.
  • We may want the pit changes recorded so we can see how much we need to improve.
  • 10 people per car, per team (through this is depending on category, the other categorys have more people per team)
  • There may have to be 2 teams being recorded on the same computer (laptop actually).
  • An ability to export to an external file (I can export to a text file easily, my recent work on the airfoil plotter (see my website, below) taught me how to do this) -----For this purpose, something i can use a for loop would work best?
  • A lap limit of at least up to 400 laps (we did 338 last year, always aim high :P)
  • A lap time limit of up to 1 hour (There may be safety issues that keep the drivers sitting in the car doing nothing for ages)
  • An easy way to get each drivers lap times separately.

Well, I think thats it. I make heavy use of the time.time() function in the version i’ve got so far. However, so far, there’s an error after 1 hour, which is what i’m heading off to fix now

Here is some of the code. Now i am having a little bit of trouble with the convert function. Basically it’s saying that it can’t find the global function convert. I don’t think i need the convert function to tie properly into the class.

edit: I solved this in the code below. I just added a self. to the start of convert. I’ve updated it in the code below as well.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# generated by wxGlade 0.6.3 on Mon Dec  7 09:49:42 2009

import wx
import time         #i added this

# begin wxGlade: extracode
# end wxGlade



class MyFrame(wx.Frame):
    def __init__(self, *args, **kwds):
        # begin wxGlade: MyFrame.__init__
        kwds["style"] = wx.DEFAULT_FRAME_STYLE
        wx.Frame.__init__(self, *args, **kwds)
        self.totTime = wx.StaticText(self, -1, "Race has not started")
        self.startTimer = wx.Button(self, -1, "Click here to start the race timer")
        self.drivers = wx.ComboBox(self, -1, choices=["driver", "p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8", "p9"], style=wx.CB_DROPDOWN)
        self.newLapb = wx.Button(self, -1, "Click here to log another lap")

        self.__set_properties()
        self.__do_layout()
        # end wxGlade

        self.newLapb.Bind(wx.EVT_BUTTON, self.newLap)
        self.startTimer.Bind(wx.EVT_BUTTON, self.start)

    def __set_properties(self):
        # begin wxGlade: MyFrame.__set_properties
        self.SetTitle("frame_1")
        self.totTime.SetMinSize((141, 20))
        self.drivers.SetSelection(0)
        # end wxGlade

    def __do_layout(self):
        # begin wxGlade: MyFrame.__do_layout
        sizer_1 = wx.BoxSizer(wx.VERTICAL)
        sizer_1.Add(self.totTime, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ADJUST_MINSIZE, 0)
        sizer_1.Add(self.startTimer, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ADJUST_MINSIZE, 0)
        sizer_1.Add(self.drivers, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ADJUST_MINSIZE, 0)
        sizer_1.Add(self.newLapb, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ADJUST_MINSIZE, 0)
        self.SetSizer(sizer_1)
        sizer_1.Fit(self)
        self.Layout()
        # end wxGlade

    def update(self, event):
        self.runTimeS = time.time() - self.butcheredTime
        if self.runTimeS >= 60:
            self.runTimeM += self.runTimeS / 60
            self.butcheredTime += 60
        if self.runTimeM >= 60:
            self.runTimeH += self.runTimeM / 60
            self.runTimeM = 0
            self.butcheredTime += 1000
        
        self.runTime = "Hours: %i, Minutes: %i, Seconds: %i" % (self.runTimeH, self.runTimeM, self.runTimeS)
        self.totTime.SetLabel(str(self.runTime))

    def newLap(self, event):
        print self.drivers.GetString(self.drivers.GetSelection())
        timeS, timeM, timeH = self.convert(oldlap=self.startTime, newlap=time.time())
        print timeS
        print timeM
        print timeH

    def start(self, event):
        #set up data structures that contain all the lap times, etc
        self.driverlaps = []
        self.p1 = []

        self.timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.update, self.timer)
        self.timer.Start(1000)
        self.totTime.SetLabel("Hasn't started yet")
        self.selected = "hayden"

        #set running time variables
        self.startTime = time.time()
        self.butcheredTime = self.startTime
        self.runTimeS = 0
        self.runTimeM = 0
        self.runTimeH = 0
        self.runTime = 0

    def convert(self, oldlap, newlap):
        """Converts a time as taken from time.time() and returns it properly formatted in hours, minutes, seconds"""
        timeS = newlap - oldlap
        timeM = 0
        timeH = 0
        if timeS >= 60:
            timeM += timeS / 60
        if timeM >= 60:
            timeH += timeM / 60
            timeM = 0
        return timeS, timeM, timeH

# end of class MyFrame


if __name__ == "__main__":
    app = wx.PySimpleApp(0)
    wx.InitAllImageHandlers()
    frame_1 = MyFrame(None, -1, "")
    app.SetTopWindow(frame_1)
    frame_1.Show()
    app.MainLoop()
    

I’ve got a fair bit further. I’ve hence decided to use a big freaking list for storing the data. If you look at the newLap() function below, you’ll see what I did. (Oh yeah, and in this version, the laps are added below in static texts, as well as being in a scroll segment)

New problem time:
Adding laps when you’re scrolled all the way up is no problem, they appear perfectly. However, when you’re scrolled down, the laps do not get shown on the gui. They are still added to the MOAL (Mother of all lists)

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# generated by wxGlade 0.6.3 on Mon Dec  7 17:40:00 2009

import wx
import time

# begin wxGlade: extracode
# end wxGlade



class MyFrame(wx.Frame):
    def __init__(self, *args, **kwds):
        # begin wxGlade: MyFrame.__init__
        kwds["style"] = wx.DEFAULT_FRAME_STYLE
        wx.Frame.__init__(self, *args, **kwds)
        self.totTime = wx.StaticText(self, -1, "The race has not started yet.")
        self.startTimer = wx.Button(self, -1, "Click here to start the race.")
        self.drivers = wx.ComboBox(self, -1, choices=["d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9", "d10"], style=wx.CB_DROPDOWN)
        self.newLapb = wx.Button(self, -1, "Click here to log a new lap")
        self.score = wx.ScrolledWindow(self, -1, style=wx.TAB_TRAVERSAL)

        self.__set_properties()
        self.__do_layout()
        # end wxGlade


        self.newLapb.Bind(wx.EVT_BUTTON, self.newLap)
        self.startTimer.Bind(wx.EVT_BUTTON, self.start)


        dy = 100
        y = 1000
        tc=wx.TextCtrl(self.score,-1, pos=wx.Point(5+10+5, 800))
        self.score.SetScrollbars(0,10,0,1000)

    def __set_properties(self):
        # begin wxGlade: MyFrame.__set_properties
        self.SetTitle("frame_1")
        self.SetSize((800, 600))
        self.drivers.SetSelection(-1)
        self.score.SetMinSize((400, 295))
        self.score.SetScrollRate(10, 10)
        # end wxGlade

    def __do_layout(self):
        # begin wxGlade: MyFrame.__do_layout
        sizer_1 = wx.BoxSizer(wx.VERTICAL)
        sizer_1.Add(self.totTime, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ADJUST_MINSIZE, 0)
        sizer_1.Add(self.startTimer, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ADJUST_MINSIZE, 0)
        sizer_1.Add(self.drivers, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ADJUST_MINSIZE, 0)
        sizer_1.Add(self.newLapb, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ADJUST_MINSIZE, 0)
        sizer_1.Add(self.score, 1, wx.EXPAND, 0)
        self.SetSizer(sizer_1)
        self.Layout()
        # end wxGlade

    def update(self, event):
        self.runTimeS = time.time() - self.butcheredTime
        if self.runTimeS >= 60:
            self.runTimeM += self.runTimeS / 60
            self.butcheredTime += 60
        if self.runTimeM >= 60:
            self.runTimeH += self.runTimeM / 60
            self.runTimeM = 0
        
        self.runTime = "Hours: %i, Minutes: %i, Seconds: %i" % (self.runTimeH, self.runTimeM, self.runTimeS)
        self.totTime.SetLabel(str(self.runTime))

    def newLap(self, event):
        self.lapNo += 1
        driver = self.drivers.GetString(self.drivers.GetSelection())
        timeS, timeM, timeH, self.oldlap = self.convert(oldlap=self.oldlap, newlap=time.time())
        lapTime = [driver, timeH, timeM, timeS]
        displayLap = [str(driver), int(timeH), int(timeM), int(timeS)]
        self.allLaps.append(lapTime)
        st=wx.StaticText(self.score, -1, str([self.lapNo, self.xpos, self.ypos]), pos=wx.Point(self.xpos,self.ypos))
        self.ypos += 20


        print self.allLaps


    def start(self, event):
        #set up data structures that contain all the lap times, etc
        self.driverlaps = []
        self.p1 = []
        self.allLaps = []

        self.timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.update, self.timer)
        self.timer.Start(1000)
        self.totTime.SetLabel("Hasn't started yet")
        self.selected = "hayden"

        #set running time variables
        self.startTime = time.time()
        self.butcheredTime = self.startTime
        self.runTimeS = 0
        self.runTimeM = 0
        self.runTimeH = 0
        self.runTime = 0

        #Other stuff
        self.oldlap = self.startTime
        self.lapNo = 0
        self.xpos = 400
        self.ypos = 200

    def convert(self, oldlap, newlap):
        """Converts a time as taken from time.time() and returns it properly formatted in hours, minutes, seconds"""
        timeS = newlap - oldlap
        timeM = 0
        timeH = 0
        if timeS >= 60:
            timeM = int(timeS / 60)
            timeS -= timeM * 60
        if timeM >= 60:
            timeH = timeM / 60
            timeM = 0
        return timeS, timeM, timeH, newlap


# end of class MyFrame


if __name__ == "__main__":
    app = wx.PySimpleApp(0)
    wx.InitAllImageHandlers()
    frame_1 = MyFrame(None, -1, "")
    app.SetTopWindow(frame_1)
    frame_1.Show()
    app.MainLoop()