Added option to log accelerometer data to file
[pedometerwidget] / pedometer_widget_home.py
1 import gtk
2 import hildondesktop
3 import gobject
4 import os
5 import time
6 import hildon
7 import gnome.gconf as gconf
8
9 print "!!!!"
10 #gobject.threads_init()
11
12 PATH="/apps/pedometerhomewidget"
13 COUNTER=PATH+"/counter"
14 TIMER=PATH+"/timer"
15 MODE=PATH+"/mode"
16 HEIGHT=PATH+"/height"
17 UNIT=PATH+"/unit"
18 ASPECT=PATH+"/aspect"
19 LOGGING=PATH+"/logging"
20
21 class PedometerHomePlugin(hildondesktop.HomePluginItem):
22     button = None
23
24     #labels for current steps
25     labelsC = { "timer" : None, "count" : None, "dist" : None, "avgSpeed" : None }
26
27     #labels for all time steps
28     labelsT = { "timer" : None, "count" : None, "dist" : None, "avgSpeed" : None }
29
30     pedometer = None
31     startTime = None
32
33     totalCounter = 0
34     totalTime = 0
35     mode = 0
36     height = 0
37     unit = 0
38
39     counter = 0
40     time = 0
41     aspect = 0
42     logging = False
43
44     def __init__(self):
45
46         gtk.gdk.threads_init()
47         hildondesktop.HomePluginItem.__init__(self)
48
49         self.client = gconf.client_get_default()
50         try:
51             self.totalCounter = self.client.get_int(COUNTER)
52             self.totalTime = self.client.get_int(TIMER)
53             self.mode = self.client.get_int(MODE)
54             self.height = self.client.get_int(HEIGHT)
55             self.unit = self.client.get_int(UNIT)
56             self.aspect = self.client.get_int(ASPECT)
57             self.logging = self.client.get_bool(LOGGING)
58         except:
59             self.client.set_int(COUNTER, 0)
60             self.client.set_int(TIMER, 0)
61             self.client.set_int(MODE, 0)
62             self.client.set_int(HEIGHT, 0)
63             self.client.set_int(UNIT, 0)
64             self.client.set_int(ASPECT, 0)
65             self.client.set_bool(LOGGING, False)
66
67         self.pedometer = PedoCounter(self.update_values)
68         self.pedometer.set_mode(self.mode)
69         self.pedometer.set_height(self.height)
70
71         self.button = gtk.Button("Start")
72         self.button.connect("clicked", self.button_clicked)
73
74         self.create_labels(self.labelsC)
75         self.create_labels(self.labelsT)
76
77         self.update_ui_values(self.labelsC, 0, 0)
78         self.update_ui_values(self.labelsT, self.totalTime, self.totalCounter)
79
80         mainHBox = gtk.HBox(spacing=1)
81
82         descVBox = gtk.VBox(spacing=1)
83         descVBox.add(gtk.Label())
84         descVBox.add(gtk.Label("Time:"))
85         descVBox.add(gtk.Label("Steps:"))
86         descVBox.add(gtk.Label("Distance:"))
87         descVBox.add(gtk.Label("Avg Speed:"))
88
89         currentVBox = gtk.VBox(spacing=1)
90         currentVBox.add(gtk.Label("Current"))
91         currentVBox.add(self.labelsC["timer"])
92         currentVBox.add(self.labelsC["count"])
93         currentVBox.add(self.labelsC["dist"])
94         currentVBox.add(self.labelsC["avgSpeed"])
95         self.currentBox = currentVBox
96
97         totalVBox = gtk.VBox(spacing=1)
98         totalVBox.add(gtk.Label("Total"))
99         totalVBox.add(self.labelsT["timer"])
100         totalVBox.add(self.labelsT["count"])
101         totalVBox.add(self.labelsT["dist"])
102         totalVBox.add(self.labelsT["avgSpeed"])
103         self.totalBox = totalVBox
104
105         mainHBox.add(self.button)
106         mainHBox.add(descVBox)
107         mainHBox.add(currentVBox)
108         mainHBox.add(totalVBox)
109
110         self.mainhbox = mainHBox
111
112         mainHBox.show_all()
113         self.add(mainHBox)
114         self.update_aspect()
115
116         self.connect("unrealize", self.close_requested)
117         self.set_settings(True)
118         self.connect("show-settings", self.show_settings)
119
120     def create_labels(self, labels):
121         labels["timer"] = gtk.Label()
122         labels["count"] = gtk.Label()
123         labels["dist"] = gtk.Label()
124         labels["avgSpeed"] = gtk.Label()
125
126     def update_aspect(self):
127         if self.aspect == 0:
128             self.currentBox.show_all()
129             self.totalBox.show_all()
130         elif self.aspect == 1:
131             self.currentBox.show_all()
132             self.totalBox.hide_all()
133         else:
134             self.currentBox.hide_all()
135             self.totalBox.show_all()
136
137     def update_ui_values(self, labels, timer, steps):
138         def get_str_distance(meters):
139             if meters > 1000:
140                 if self.unit == 0:
141                     return "%.2f km" % (meters/1000)
142                 else:
143                     return "%.2f mi" % (meters/1609.344)
144             else:
145                 if self.unit == 0:
146                     return "%d m" % meters
147                 else:
148                     return "%d ft" % int(meters*3.2808)
149
150         def get_avg_speed(timer, dist):
151             suffix = ""
152             conv = 0
153             if self.unit:
154                 suffix = "mi/h"
155                 conv = 2.23693629
156             else:
157                 suffix = "km/h"
158                 conv = 3.6
159
160             if timer == 0:
161                 return "N/A " + suffix
162             speed = 1.0 *dist / timer
163             #convert from meters per second to km/h or mi/h
164             speed *= conv
165             return "%.2f %s" % (speed, suffix)
166
167         tdelta = timer
168         hours = int(tdelta / 3600)
169         tdelta -= 3600 * hours
170         mins = int(tdelta / 60)
171         tdelta -= 60 * mins
172         secs = int(tdelta)
173
174         strtime = "%.2d:%.2d:%.2d" % ( hours, mins, secs)
175
176         labels["timer"].set_label(strtime)
177         labels["count"].set_label(str(steps))
178
179         dist = self.pedometer.get_distance(steps)
180
181         labels["dist"].set_label(get_str_distance(dist))
182         labels["avgSpeed"].set_label(get_avg_speed(timer, dist))
183
184     def update_current(self):
185         self.update_ui_values(self.labelsC, self.time, self.counter)
186
187     def update_total(self):
188         self.update_ui_values(self.labelsT, self.totalTime, self.totalCounter)
189
190     def show_settings(self, widget):
191         def reset_total_counter(arg):
192             widget.totalCounter = 0
193             widget.totalTime = 0
194             widget.update_total()
195             hildon.hildon_banner_show_information(self,"None", "Total counter was resetted")
196
197         def selector_changed(selector, data):
198             widget.mode = selector.get_active(0)
199             widget.client.set_int(MODE, widget.mode)
200
201         def selectorH_changed(selector, data):
202             widget.height = selectorH.get_active(0)
203             widget.client.set_int(HEIGHT, widget.height)
204
205         def selectorUnit_changed(selector, data):
206             widget.unit = selectorUnit.get_active(0)
207             widget.client.set_int(UNIT, widget.unit)
208             widget.update_current()
209             widget.update_total()
210
211         def selectorUI_changed(selector, data):
212             widget.aspect = selectorUI.get_active(0)
213             widget.client.set_int(ASPECT, widget.aspect)
214             widget.update_aspect()
215
216         def logButton_changed(checkButton):
217             widget.logging = checkButton.get_active()
218             print "logButton"
219             print widget.logging
220             widget.client.set_bool(LOGGING, widget.logging)
221
222         dialog = gtk.Dialog()
223         dialog.set_transient_for(self)
224         dialog.set_title("Settings")
225
226         dialog.add_button("OK", gtk.RESPONSE_OK)
227         button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
228         button.set_title("Reset total counter")
229         button.set_alignment(0, 0.8, 1, 1)
230         button.connect("clicked", reset_total_counter)
231
232         selector = hildon.TouchSelector(text=True)
233         selector.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
234         selector.append_text("Walk")
235         selector.append_text("Run")
236         selector.connect("changed", selector_changed)
237
238         modePicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
239         modePicker.set_alignment(0.0, 0.5, 1.0, 1.0)
240         modePicker.set_title("Select mode")
241         modePicker.set_selector(selector)
242         modePicker.set_active(widget.mode)
243
244         selectorH = hildon.TouchSelector(text=True)
245         selectorH.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
246         selectorH.append_text("< 1.50 m")
247         selectorH.append_text("1.50 - 1.65 m")
248         selectorH.append_text("1.66 - 1.80 m")
249         selectorH.append_text("1.81 - 1.95 m")
250         selectorH.append_text(" > 1.95 m")
251         selectorH.connect("changed", selectorH_changed)
252
253         heightPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
254         heightPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
255         heightPicker.set_title("Select height")
256         heightPicker.set_selector(selectorH)
257         heightPicker.set_active(widget.height)
258
259         selectorUnit = hildon.TouchSelector(text=True)
260         selectorUnit.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
261         selectorUnit.append_text("Metric (km)")
262         selectorUnit.append_text("English (mi)")
263         selectorUnit.connect("changed", selectorUnit_changed)
264
265         unitPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
266         unitPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
267         unitPicker.set_title("Units")
268         unitPicker.set_selector(selectorUnit)
269         unitPicker.set_active(widget.unit)
270
271         selectorUI = hildon.TouchSelector(text=True)
272         selectorUI = hildon.TouchSelector(text=True)
273         selectorUI.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
274         selectorUI.append_text("Show current + total")
275         selectorUI.append_text("Show only current")
276         selectorUI.append_text("Show only total")
277         selectorUI.connect("changed", selectorUI_changed)
278
279         UIPicker = hildon.PickerButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
280         UIPicker.set_alignment(0.0, 0.5, 1.0, 1.0)
281         UIPicker.set_title("Widget aspect")
282         UIPicker.set_selector(selectorUI)
283         UIPicker.set_active(widget.aspect)
284
285         logButton = hildon.CheckButton(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
286         logButton.set_label("Log data")
287         logButton.set_active(widget.logging)
288         logButton.connect("toggled", logButton_changed)
289
290         dialog.vbox.add(button)
291         dialog.vbox.add(modePicker)
292         dialog.vbox.add(heightPicker)
293         dialog.vbox.add(unitPicker)
294         dialog.vbox.add(UIPicker)
295         dialog.vbox.add(logButton)
296
297         dialog.show_all()
298         response = dialog.run()
299         hildon.hildon_banner_show_information(self, "None", "You have to Stop/Start the counter to apply the new settings")
300         dialog.destroy()
301
302     def close_requested(self, widget):
303         if self.pedometer is None:
304             return
305
306         self.pedometer.request_stop()
307         if self.pedometer.isAlive():
308             self.pedometer.join()
309
310     def update_values(self, totalCurent, lastInterval):
311         self.totalCounter += lastInterval
312         self.counter = totalCurent
313
314         tdelta = time.time() - self.time - self.startTime
315         self.time += tdelta
316         self.totalTime += tdelta
317
318         self.update_current()
319         self.update_total()
320
321     def button_clicked(self, button):
322         print "button clicked"
323
324         if self.pedometer is not None and self.pedometer.isAlive():
325             #counter is running
326             self.pedometer.request_stop()
327             self.pedometer.join()
328             self.client.set_int(COUNTER, self.totalCounter)
329             self.client.set_int(TIMER, int(self.totalTime))
330             self.button.set_label("Start")
331         else:
332             self.pedometer = PedoCounter(self.update_values)
333             self.pedometer.set_mode(self.mode)
334             self.pedometer.set_height(self.height)
335             self.pedometer.set_logging(self.logging)
336
337             self.time = 0
338             self.counter = 0
339
340             self.update_current()
341
342             self.pedometer.start()
343             self.startTime = time.time()
344             self.button.set_label("Stop")
345
346         print "button clicked finished"
347
348 hd_plugin_type = PedometerHomePlugin
349
350 # The code below is just for testing purposes.
351 # It allows to run the widget as a standalone process.
352 if __name__ == "__main__":
353     import gobject
354     gobject.type_register(hd_plugin_type)
355     obj = gobject.new(hd_plugin_type, plugin_id="plugin_id")
356     obj.show_all()
357     gtk.main()
358
359 ############### old pedometer.py ###
360 import math
361 import logging
362
363 from threading import Thread
364
365 logger = logging.getLogger("pedometer")
366 logger.setLevel(logging.INFO)
367
368 ch = logging.StreamHandler()
369 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
370 ch.setFormatter(formatter)
371 logger.addHandler(ch)
372
373 class PedoIntervalCounter:
374     MIN_THRESHOLD = 500
375     MIN_TIME_STEPS = 0.5
376     x = []
377     y = []
378     z = []
379     t = []
380
381     #TODO: check if last detected step is at the end of the interval
382
383     def __init__(self, coords, tval):
384         self.x = coords[0]
385         self.y = coords[1]
386         self.z = coords[2]
387         self.t = tval
388
389     def setThreshold(self, value):
390         self.MIN_THRESHOLD = value
391
392     def setTimeSteps(self, value):
393         self.MIN_TIME_STEPS = value
394
395     def calc_mean(self, vals):
396         sum = 0
397         for i in vals:
398             sum+=i
399         if len(vals) > 0:
400             return sum / len(vals)
401         return 0
402
403     def calc_stdev(self, vals):
404         rez = 0
405         mean = self.calc_mean(vals)
406         for i in vals:
407             rez+=pow(abs(mean-i),2)
408         return math.sqrt(rez/len(vals))
409
410     def calc_threshold(self, vals):
411         vmax = max(vals)
412         vmin = min(vals)
413         mean = self.calc_mean(vals)
414         threshold = max (abs(mean-vmax), abs(mean-vmin))
415         return threshold
416
417     def count_steps(self, vals, t):
418         threshold = self.MIN_THRESHOLD
419         mean = self.calc_mean(vals)
420         cnt = 0
421
422         i=0
423         while i < len(vals):
424             if abs(vals[i] - mean) > threshold:
425                 cnt+=1
426                 ntime = t[i] + 0.5
427                 while i < len(vals) and t[i] < ntime:
428                     i+=1
429             i+=1
430         return cnt
431
432     def get_best_values(self, x, y, z):
433         dev1 = self.calc_stdev(x)
434         dev2 = self.calc_stdev(y)
435         dev3 = self.calc_stdev(z)
436         dev_max = max(dev1, dev2, dev3)
437
438         if ( abs(dev1 - dev_max ) < 0.001):
439             logger.info("X chosen as best axis, stdev %f" % dev1)
440             return x
441         elif (abs(dev2 - dev_max) < 0.001):
442             logger.info("Y chosen as best axis, stdev %f" % dev2)
443             return y
444         else:
445             logger.info("Z chosen as best axis, stdev %f" % dev3)
446             return z
447
448     def number_steps(self):
449         vals = self.get_best_values(self.x, self.y, self.z)
450         return self.count_steps(vals, self.t)
451
452 class PedoCounter(Thread):
453     COORD_FNAME = "/sys/class/i2c-adapter/i2c-3/3-001d/coord"
454     COORD_FNAME_SDK = "/home/andrei/pedometer-widget-0.1/date.txt"
455     LOGFILE = "/home/user/log_pedometer"
456     COORD_GET_INTERVAL = 0.01
457     COUNT_INTERVAL = 5
458
459     STEP_LENGTH = 0.7
460
461     MIN_THRESHOLD = 500
462     MIN_TIME_STEPS = 0.5
463
464     counter = 0
465     stop_requested = False
466     update_function = None
467     logging = False
468
469     def __init__(self, update_function = None):
470         Thread.__init__(self)
471         if not os.path.exists(self.COORD_FNAME):
472             self.COORD_FNAME = self.COORD_FNAME_SDK
473
474         self.update_function = update_function
475
476     def set_mode(self, mode):
477         #runnig, higher threshold to prevent fake steps
478         if mode == 1:
479             self.MIN_THRESHOLD = 650
480             self.MIN_TIME_STEPS = 0.35
481         #walking
482         else:
483             self.MIN_THRESHOLD = 500
484             self.MIN_TIME_STEPS = 0.5
485
486     def set_logging(self, value):
487         self.logging = value
488
489     #set height, will affect the distance
490     def set_height(self, height_interval):
491         if height_interval == 0:
492             STEP_LENGTH = 0.59
493         elif height_interval == 1:
494             STEP_LENGTH = 0.64
495         elif height_interval == 2:
496             STEP_LENGTH = 0.71
497         elif height_interval == 3:
498             STEP_LENGTH = 0.77
499         elif height_interval == 4:
500             STEP_LENGTH = 0.83
501
502     def get_rotation(self):
503         f = open(self.COORD_FNAME, 'r')
504         coords = [int(w) for w in f.readline().split()]
505         f.close()
506         return coords
507
508     def reset_counter(self):
509         counter = 0
510
511     def get_counter(self):
512         return counter
513
514     def start_interval(self):
515         logger.info("New interval started")
516         stime = time.time()
517         t=[]
518         coords = [[], [], []]
519         while not self.stop_requested and (len(t) == 0 or t[-1] < 5):
520             x,y,z = self.get_rotation()
521             coords[0].append(int(x))
522             coords[1].append(int(y))
523             coords[2].append(int(z))
524             now = time.time()-stime
525             if self.logging:
526                 self.file.write("%d %d %d %f\n" %(coords[0][-1], coords[1][-1], coords[2][-1], now))
527
528             t.append(now)
529             time.sleep(self.COORD_GET_INTERVAL)
530         pic = PedoIntervalCounter(coords, t)
531         cnt = pic.number_steps()
532
533         logger.info("Number of steps detected for last interval %d, number of coords: %d" % (cnt, len(t)))
534
535         self.counter += cnt
536         logger.info("Total number of steps : %d" % self.counter)
537         return cnt
538
539     def request_stop(self):
540         self.stop_requested = True
541
542     def run(self):
543         logger.info("Thread started")
544         print self.logging
545         if self.logging:
546             fname = "%d_%d_%d_%d_%d_%d" % time.localtime()[0:6]
547             self.file = open(self.LOGFILE + fname + ".txt", "w")
548
549         while 1 and not self.stop_requested:
550             last_cnt = self.start_interval()
551             if self.update_function is not None:
552                 gobject.idle_add(self.update_function, self.counter, last_cnt)
553
554         if self.logging:
555             self.file.close()
556
557         logger.info("Thread has finished")
558
559     def get_distance(self, steps=None):
560         if steps == None:
561             steps = self.counter
562         return self.STEP_LENGTH * steps;
563