wobble.py (4945B)
1 import BLE_GATT 2 from gi.repository import GLib 3 4 import pdb 5 import struct 6 7 import sys 8 import time 9 from datetime import datetime, timedelta 10 import os 11 12 import pygame 13 import pygame.freetype 14 from pygame.locals import * 15 16 class CadenceListener: 17 prevCtime = 0 18 prevWtime = 0 19 prevWrevs = 0 20 prevCrevs = 0 21 wheelSpeed = 0 22 crankSpeed = 0 23 crankSpeedMax = 120 24 elapsedTime = None 25 firstMessage = True 26 27 def getWheelSpeed(self): 28 return self.wheelSpeed 29 30 def process(self, value): 31 flags = value[0] 32 offset = 1 33 if flags & 1: 34 wheelRevolutions,wheelTime = struct.unpack('<IH', bytes(value)[offset:offset+6]) 35 offset = offset + 6 36 if flags & 2: 37 crankRevolutions,crankTime = struct.unpack('<HH', bytes(value)[offset:offset+4]) 38 39 if(self.firstMessage): 40 self.prevCtime = crankTime 41 self.prevWtime = wheelTime 42 self.prevCrevs = crankRevolutions 43 self.prevWrevs = wheelRevolutions 44 self.firstMessage = False 45 return 46 47 # handle wraparound 48 if(crankTime < self.prevCtime): 49 self.prevCtime = self.prevCtime - 65536 50 if(wheelTime < self.prevWtime): 51 self.prevWtime = self.prevWtime - 65536 52 if(crankRevolutions < self.prevCrevs): 53 self.prevCrevs = self.prevCrevs - 65536 54 if(wheelRevolutions < self.prevWrevs): 55 self.prevWrevs = self.prevWrevs - 65536 * 65536 56 57 if(crankTime > self.prevCtime): 58 self.crankSpeed = 60 * 1024 * ((crankRevolutions - self.prevCrevs) / 59 float(crankTime - self.prevCtime)) 60 print("crank", self.crankSpeed) 61 self.prevCtime = crankTime 62 self.prevCrevs = crankRevolutions 63 64 if(wheelTime > self.prevWtime): 65 millis = float(wheelTime - self.prevWtime) 66 self.rolling = True 67 revs = (wheelRevolutions - self.prevWrevs) / millis 68 69 self.wheelSpeed = revs * 2205 * 3600 / 1024.0 70 print("wheel", millis, self.wheelSpeed) 71 self.prevWtime = wheelTime 72 self.prevWrevs = wheelRevolutions 73 self.elapsedTime = (self.elapsedTime or 0) + (millis / 1024.0) 74 75 class Wobble: 76 listener = CadenceListener() 77 78 def __init__(self): 79 pygame.init() 80 self.surface = pygame.display.set_mode((600,300), pygame.RESIZABLE) 81 self.width = self.surface.get_width(); 82 self.height = self.surface.get_height(); 83 self.font = None 84 85 def on_update(self): 86 scale = int(min(self.width/7, (self.height-20)/3)) 87 if(self.font == None): 88 self.font = pygame.freetype.Font(os.environ['FONT'], scale) 89 90 self.surface.fill((10,20,10)) 91 self.font.render_to(self.surface, 92 (scale/16,10), 93 "{: 5.1f} km/h".format(self.listener.wheelSpeed), 94 (0,255,0)) 95 self.font.render_to(self.surface, 96 (scale/16,scale + 20), 97 "{: 5.1f} rpm".format(self.listener.crankSpeed), 98 (0,255,0)) 99 if self.listener.elapsedTime != None: 100 runTime = timedelta(seconds=self.listener.elapsedTime) 101 timelabel = str(runTime)[0:9] 102 else: 103 timelabel = datetime.now().strftime("%H:%M:%S") 104 self.font.render_to(self.surface, 105 (scale, scale * 2 + 30), 106 timelabel, 107 (0,255,0)) 108 pix = self.width *((self.listener.crankSpeed-40)/ self.listener.crankSpeedMax) 109 pix = max(pix, 0) 110 pygame.draw.rect(self.surface, (180,0,0), pygame.Rect(0,self.height/2-60, pix, 50)) 111 pygame.draw.rect(self.surface, (0,0,180), pygame.Rect(pix,self.height/2-60, 20, 50)) 112 pygame.display.flip() 113 114 115 bike = BLE_GATT.Central(sys.argv[1]) 116 bike.connect() 117 app = Wobble() 118 119 csc_measurement = '00002a5b-0000-1000-8000-00805F9B34FB' 120 bike.on_value_change(csc_measurement, app.listener.process) 121 122 running = True 123 124 def handle_pygame_event(event): 125 if event.type == pygame.QUIT: 126 running = False 127 if event.type == pygame.KEYUP and event.key == pygame.K_q: 128 running = False 129 if event.type == pygame.KEYUP and event.key == pygame.K_SPACE: 130 if app.listener.startTime == None: 131 app.listener.startTime = datetime.now() 132 else: 133 app.listener.startTime = None 134 if event.type == pygame.VIDEORESIZE: 135 app.width = event.w 136 app.height = event.h 137 app.font = None 138 139 def on_timer(): 140 handle_pygame_event(pygame.event.poll()) 141 app.on_update() 142 return True 143 144 145 GLib.timeout_add(100, on_timer) 146 147 try: 148 # Time to go live 149 print("Listening for events...") 150 bike.mainloop.run() 151 152 finally: 153 pygame.quit() 154