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