Compare commits

...

4 Commits

2 changed files with 64 additions and 40 deletions

View File

@ -7,3 +7,24 @@ Perhaps useful if you have a stationary bike of some kind and want to
put the vital statistics on a big screen instead of on your handlebar. put the vital statistics on a big screen instead of on your handlebar.
![screenshot](20221209_22h46m07s_grim.png) ![screenshot](20221209_22h46m07s_grim.png)
## How to use
First you need to identify bluetooth address of your bike sensor(s).
Spin the wheel to wake it up, then run
```sh
$ bluetoothctl scan on
Discovery started
[CHG] Controller 24:FD:52:00:ED:F1 Discovering: yes
[NEW] Device C2:35:5D:F2:F4:0F Giant Combo
[CHG] Device 98:06:3A:15:B7:BA RSSI: -82
^C
$ bluetoothctl connect C2:35:5D:F2:F4:0F
```
Now you can start the app
```sh
$ python wobble.py C2:35:5D:F2:F4:0F
```

View File

@ -1,11 +1,12 @@
import BLE_GATT import BLE_GATT
from gi.repository import GLib from gi.repository import GLib
import pdb import pdb
import struct import struct
import sys import sys
import time import time
from datetime import datetime from datetime import datetime, timedelta
import os import os
import pygame import pygame
@ -13,8 +14,6 @@ import pygame.freetype
from pygame.locals import * from pygame.locals import *
class CadenceListener: class CadenceListener:
csc_feature = '00002a5c-0000-1000-8000-00805F9B34FB'
prevCtime = 0 prevCtime = 0
prevWtime = 0 prevWtime = 0
prevWrevs = 0 prevWrevs = 0
@ -22,7 +21,8 @@ class CadenceListener:
wheelSpeed = 0 wheelSpeed = 0
crankSpeed = 0 crankSpeed = 0
crankSpeedMax = 120 crankSpeedMax = 120
startTime = None elapsedTime = None
firstMessage = True
def getWheelSpeed(self): def getWheelSpeed(self):
return self.wheelSpeed return self.wheelSpeed
@ -35,13 +35,13 @@ class CadenceListener:
offset = offset + 6 offset = offset + 6
if flags & 2: if flags & 2:
crankRevolutions,crankTime = struct.unpack('<HH', bytes(value)[offset:offset+4]) crankRevolutions,crankTime = struct.unpack('<HH', bytes(value)[offset:offset+4])
# print(wheelrevs, wheeltime, crankrevs, cranktime)
if(not self.startTime): if(self.firstMessage):
self.prevCtime = crankTime self.prevCtime = crankTime
self.prevWtime = wheelTime self.prevWtime = wheelTime
self.prevCrevs = crankRevolutions self.prevCrevs = crankRevolutions
self.prevWrevs = wheelRevolutions self.prevWrevs = wheelRevolutions
self.startTime = datetime.now() self.firstMessage = False
return return
# handle wraparound # handle wraparound
@ -62,16 +62,17 @@ class CadenceListener:
self.prevCrevs = crankRevolutions self.prevCrevs = crankRevolutions
if(wheelTime > self.prevWtime): if(wheelTime > self.prevWtime):
revs = ((wheelRevolutions - self.prevWrevs) / millis = float(wheelTime - self.prevWtime)
float(wheelTime - self.prevWtime)) self.rolling = True
revs = (wheelRevolutions - self.prevWrevs) / millis
self.wheelSpeed = revs * 2205 * 3600 / 1024.0 self.wheelSpeed = revs * 2205 * 3600 / 1024.0
print("wheel", print("wheel", millis, self.wheelSpeed)
(float(wheelTime - self.prevWtime)),
self.wheelSpeed)
self.prevWtime = wheelTime self.prevWtime = wheelTime
self.prevWrevs = wheelRevolutions self.prevWrevs = wheelRevolutions
self.elapsedTime = (self.elapsedTime or 0) + (millis / 1024.0)
class Biscuit: class Wobble:
listener = CadenceListener() listener = CadenceListener()
def __init__(self): def __init__(self):
@ -95,8 +96,8 @@ class Biscuit:
(scale/16,scale + 20), (scale/16,scale + 20),
"{: 5.1f} rpm".format(self.listener.crankSpeed), "{: 5.1f} rpm".format(self.listener.crankSpeed),
(0,255,0)) (0,255,0))
if self.listener.startTime != None: if self.listener.elapsedTime != None:
runTime = datetime.now() - self.listener.startTime runTime = timedelta(seconds=self.listener.elapsedTime)
timelabel = str(runTime)[0:9] timelabel = str(runTime)[0:9]
else: else:
timelabel = datetime.now().strftime("%H:%M:%S") timelabel = datetime.now().strftime("%H:%M:%S")
@ -111,41 +112,43 @@ class Biscuit:
pygame.display.flip() pygame.display.flip()
bike = BLE_GATT.Central(sys.argv[1])
bike_address = 'C2:35:5D:F2:F4:0F'
bike = BLE_GATT.Central(bike_address)
bike.connect() bike.connect()
app = Biscuit() app = Wobble()
csc_measurement = '00002a5b-0000-1000-8000-00805F9B34FB' csc_measurement = '00002a5b-0000-1000-8000-00805F9B34FB'
bike.on_value_change(csc_measurement, app.listener.process) bike.on_value_change(csc_measurement, app.listener.process)
running = True running = True
def handle_pygame_event(event):
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYUP and event.key == pygame.K_q:
running = False
if event.type == pygame.KEYUP and event.key == pygame.K_SPACE:
if app.listener.startTime == None:
app.listener.startTime = datetime.now()
else:
app.listener.startTime = None
if event.type == pygame.VIDEORESIZE:
app.width = event.w
app.height = event.h
app.font = None
def on_timer():
handle_pygame_event(pygame.event.poll())
app.on_update()
return True
GLib.timeout_add(100, on_timer)
try: try:
# Time to go live # Time to go live
context = bike.mainloop.get_context()
print("Listening for events...") print("Listening for events...")
while running: bike.mainloop.run()
time.sleep(0.1)
context.iteration(False)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYUP and event.key == pygame.K_q:
running = False
if event.type == pygame.KEYUP and event.key == pygame.K_SPACE:
if app.listener.startTime == None:
app.listener.startTime = datetime.now()
else:
app.listener.startTime = None
if event.type == pygame.VIDEORESIZE:
app.width = event.w
app.height = event.h
app.font = None
app.on_update()
finally: finally:
pygame.quit() pygame.quit()